Binary files /tmp/p5NqK7BXRl/ubuntuone-control-panel-2.99.90/data/external_icon_orange.png and /tmp/eEalyble9a/ubuntuone-control-panel-2.99.91/data/external_icon_orange.png differ Binary files /tmp/p5NqK7BXRl/ubuntuone-control-panel-2.99.90/data/external_icon_white.png and /tmp/eEalyble9a/ubuntuone-control-panel-2.99.91/data/external_icon_white.png differ diff -Nru ubuntuone-control-panel-2.99.90/data/qt/are_you_sure.ui ubuntuone-control-panel-2.99.91/data/qt/are_you_sure.ui --- ubuntuone-control-panel-2.99.90/data/qt/are_you_sure.ui 1970-01-01 00:00:00.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/are_you_sure.ui 2012-03-21 14:06:28.000000000 +0000 @@ -0,0 +1,153 @@ + + + Dialog + + + + 0 + 0 + 409 + 196 + + + + + + + Are you sure? + + + Qt::PlainText + + + + + + + more explanation + + + Qt::AutoText + + + true + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Yeah + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Nopes + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + no_button + clicked() + Dialog + reject() + + + 272 + 174 + + + 330 + 129 + + + + + yes_button + clicked() + Dialog + accept() + + + 118 + 167 + + + 163 + 122 + + + + + diff -Nru ubuntuone-control-panel-2.99.90/data/qt/controlpanel.ui ubuntuone-control-panel-2.99.91/data/qt/controlpanel.ui --- ubuntuone-control-panel-2.99.90/data/qt/controlpanel.ui 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/controlpanel.ui 2012-03-21 14:06:28.000000000 +0000 @@ -139,11 +139,6 @@ - - - 8 - - @@ -158,9 +153,9 @@ - + - Get more storage + foo bar baz true @@ -292,6 +287,9 @@ Get help online + + true + @@ -309,11 +307,6 @@ - - - 11 - - Talk to us @@ -329,8 +322,8 @@ - 16 - 16 + 20 + 20 @@ -340,6 +333,9 @@ :/twitter.png:/twitter.png + + true + @@ -352,8 +348,8 @@ - 16 - 16 + 20 + 20 @@ -363,6 +359,9 @@ :/facebook.png:/facebook.png + + true + @@ -372,6 +371,11 @@ + GetStorageButton + QPushButton +
ubuntuone.controlpanel.gui.qt.gotoweb
+
+ GoToWebButton QPushButton
ubuntuone.controlpanel.gui.qt.gotoweb
diff -Nru ubuntuone-control-panel-2.99.90/data/qt/device_remote.ui ubuntuone-control-panel-2.99.91/data/qt/device_remote.ui --- ubuntuone-control-panel-2.99.90/data/qt/device_remote.ui 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/device_remote.ui 1970-01-01 00:00:00.000000000 +0000 @@ -1,50 +0,0 @@ - - - Form - - - - 0 - 0 - 264 - 45 - - - - Form - - - - - - :/computer.png - - - - - - - Non local device - - - - - - - Qt::Horizontal - - - - 217 - 20 - - - - - - - - - - - diff -Nru ubuntuone-control-panel-2.99.90/data/qt/devices.ui ubuntuone-control-panel-2.99.91/data/qt/devices.ui --- ubuntuone-control-panel-2.99.90/data/qt/devices.ui 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/devices.ui 2012-03-21 14:06:28.000000000 +0000 @@ -21,16 +21,16 @@ 0 - + This device - + 0 - + @@ -120,6 +120,12 @@ QPushButton
ubuntuone.controlpanel.gui.qt.gotoweb
+ + DeviceWidget + QWidget +
ubuntuone.controlpanel.gui.qt.device
+ 1 +
diff -Nru ubuntuone-control-panel-2.99.90/data/qt/device.ui ubuntuone-control-panel-2.99.91/data/qt/device.ui --- ubuntuone-control-panel-2.99.90/data/qt/device.ui 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/device.ui 2012-03-21 14:06:28.000000000 +0000 @@ -6,14 +6,17 @@ 0 0 - 233 - 36 + 430 + 27 Form + + 0 + diff -Nru ubuntuone-control-panel-2.99.90/data/qt/folders.ui ubuntuone-control-panel-2.99.91/data/qt/folders.ui --- ubuntuone-control-panel-2.99.90/data/qt/folders.ui 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/folders.ui 2012-03-21 14:06:28.000000000 +0000 @@ -6,7 +6,7 @@ 0 0 - 345 + 393 279 @@ -35,7 +35,7 @@ - + 0 0 @@ -43,6 +43,9 @@ Go to the web for public and private sharing options + + true + @@ -97,7 +100,7 @@ false - true + false @@ -153,7 +156,7 @@ - Add a folder from this computer + Add a folder true @@ -161,6 +164,13 @@ + + + Check settings + + + + Qt::Horizontal diff -Nru ubuntuone-control-panel-2.99.90/data/qt/images.qrc ubuntuone-control-panel-2.99.91/data/qt/images.qrc --- ubuntuone-control-panel-2.99.90/data/qt/images.qrc 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/images.qrc 2012-03-21 14:06:28.000000000 +0000 @@ -23,5 +23,7 @@ ../Ubuntu-R.ttf ../Ubuntu-B.ttf ubuntuone.qss + linux.qss + windows.qss diff -Nru ubuntuone-control-panel-2.99.90/data/qt/linux.qss ubuntuone-control-panel-2.99.91/data/qt/linux.qss --- ubuntuone-control-panel-2.99.90/data/qt/linux.qss 1970-01-01 00:00:00.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/linux.qss 2012-03-21 14:06:28.000000000 +0000 @@ -0,0 +1,2 @@ +/* Styles specific to the linux platform */ + diff -Nru ubuntuone-control-panel-2.99.90/data/qt/loadingoverlay.ui ubuntuone-control-panel-2.99.91/data/qt/loadingoverlay.ui --- ubuntuone-control-panel-2.99.90/data/qt/loadingoverlay.ui 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/loadingoverlay.ui 2012-03-21 14:06:28.000000000 +0000 @@ -52,11 +52,6 @@ 0 - - - 14 - - Getting information, please wait... diff -Nru ubuntuone-control-panel-2.99.90/data/qt/local_folders.ui ubuntuone-control-panel-2.99.91/data/qt/local_folders.ui --- ubuntuone-control-panel-2.99.90/data/qt/local_folders.ui 1970-01-01 00:00:00.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/local_folders.ui 2012-03-21 14:06:28.000000000 +0000 @@ -0,0 +1,179 @@ + + + Form + + + + 0 + 0 + 274 + 312 + + + + + 0 + + + + + Qt::ScrollBarAlwaysOn + + + true + + + 0 + + + false + + + true + + + true + + + false + + + + Sync these folders on my computer + + + + + Space (Total) + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add a folder + + + true + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + + + 0 + + + + + overflow message + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + add storage + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + GetStorageButton + QPushButton +
ubuntuone.controlpanel.gui.qt.gotoweb
+
+ + AddFolderButton + QPushButton +
ubuntuone.controlpanel.gui.qt.addfolder
+
+
+ + +
diff -Nru ubuntuone-control-panel-2.99.90/data/qt/preferences.ui ubuntuone-control-panel-2.99.91/data/qt/preferences.ui --- ubuntuone-control-panel-2.99.90/data/qt/preferences.ui 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/preferences.ui 2012-03-21 14:06:28.000000000 +0000 @@ -6,8 +6,8 @@ 0 0 - 512 - 328 + 520 + 311 @@ -53,14 +53,27 @@
- + + + + Qt::Horizontal + + + + 40 + 20 + + + + + Limit download speed to - + -1 @@ -70,27 +83,27 @@ - + Kilobits per second - - + + - Qt::Horizontal + Qt::Vertical 40 - 20 + 10 - + Please note that your files will not sync if you set bandwidth to 0 @@ -103,19 +116,6 @@ - - - - Qt::Vertical - - - - 40 - 20 - - - -
@@ -124,32 +124,45 @@ File Sync Settings - + 0 - + Connect automatically when computer starts - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + Automatically sync all new cloud folders to this computer - + Automatically sync all folders shared with me to this computer - + Allow all notifications to this device @@ -167,7 +180,7 @@ 20 - 40 + 10 diff -Nru ubuntuone-control-panel-2.99.90/data/qt/side_widget.ui ubuntuone-control-panel-2.99.91/data/qt/side_widget.ui --- ubuntuone-control-panel-2.99.90/data/qt/side_widget.ui 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/side_widget.ui 2012-03-21 14:06:28.000000000 +0000 @@ -11,17 +11,11 @@ - + 0 0 - - - 170 - 466 - - 40 @@ -80,6 +74,12 @@ true + + + 0 + 0 + + Install @@ -116,6 +116,12 @@ true + + + 0 + 0 + + Sign In @@ -149,6 +155,12 @@ true + + + 0 + 0 + + Select sync folders @@ -182,6 +194,12 @@ true + + + 0 + 0 + + Sync, stream, share! diff -Nru ubuntuone-control-panel-2.99.90/data/qt/signin.ui ubuntuone-control-panel-2.99.91/data/qt/signin.ui --- ubuntuone-control-panel-2.99.90/data/qt/signin.ui 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/signin.ui 2012-03-21 14:06:28.000000000 +0000 @@ -72,13 +72,6 @@ - - - 11 - 50 - false - - Welcome to Ubuntu One diff -Nru ubuntuone-control-panel-2.99.90/data/qt/ubuntuone.qss ubuntuone-control-panel-2.99.91/data/qt/ubuntuone.qss --- ubuntuone-control-panel-2.99.90/data/qt/ubuntuone.qss 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/ubuntuone.qss 2012-03-21 14:06:28.000000000 +0000 @@ -1,10 +1,16 @@ +/* Common colours: + +orange: #dd4814 +dark grey: #333333 +light grey: #aea79f + +*/ + QMainWindow { background-color: #aea79f; } QWidget { - font-family: "Ubuntu"; - font-size: 13px; color: #333333; } @@ -55,10 +61,6 @@ min-height: 100px; } -QFrame#frm_box > QLabel { - font-size: 24px; -} - SideWidget { background-color: white; border-style: dotted; @@ -77,6 +79,18 @@ border-width: 1px; } +QPushButton:focus { + border-width: 2px; + /* reduce the padding since we have a 2px border now */ + padding: 5px; + padding-left: 19px; + padding-right: 19px; + /* hack to make the mild-orange focused box dissapear */ + padding-top: 100%; + padding-bottom: 100%; + /* end of hack */ +} + QPushButton:disabled { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #eaeaea, stop: 1.0 #cacaca); @@ -91,19 +105,18 @@ border-color: #999999; } -QPushButton:enabled:focus, +QPushButton:enabled:focus { + border-color: #dd4814; +} + QPushButton:enabled:hover { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffffff,stop: 1.0 #ededed); - color: #333333; - border-color: #999999; } QPushButton:enabled:pressed { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #d9d9d9,stop: 1.0 #fefefe); - color: #333333; - border-color: #999999; } QPushButton:default:enabled { @@ -113,55 +126,91 @@ border-color: #999999; } -QPushButton:default:enabled:focus, +QPushButton:default:enabled:focus { + border-color: #333333; +} + QPushButton:default:enabled:hover { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffb19c,stop: 1.0 #dd4814); - color: white; - border-color: #999999; } QPushButton:default:enabled:pressed { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #b93f14,stop: 1.0 #dd4814); - color: white; - border-color: #999999; } -QPushButton#help_button { - background: transparent; - border: none; - color: white; - text-decoration: underline; - padding: 0px; +ExploreFolderButton { + margin-top: 5px; + margin-bottom: 5px; + margin-right: 20px; + margin-left: 20px; } -QPushButton#explore_folder_button { +ExploreFolderButton:enabled { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #fbfbfb, stop: 1.0 #e6e6e6); - height: 15px; - border-radius: 7px; border-color: #908e8d; color: #595959; - padding-left: 10px; - padding-right: 10px; - margin-top: 5px; - margin-bottom: 5px; - margin-right: 20px; - margin-left: 20px; } QPushButton#twitter_button, QPushButton#facebook_button { + background: transparent; border: none; } -GoToWebButton#forgot_password_button, +QPushButton#twitter_button:focus, +QPushButton#facebook_button:focus { + border: 2px solid #dd4814; +} + +GoToWebButton#help_button { + background: transparent; + border: none; + text-decoration: underline; + color: white; + padding-left: 10px; + padding-right: 25px; + background-image: url(:/external_icon_white.png); + background-repeat: no-repeat; + background-position: right; + background-origin: margin; +} + +GoToWebButton#help_button:focus { + border: 2px solid #dd4814; + /* hack to make the mild-orange focused box dissapear */ + padding-top: 100%; + padding-bottom: 100%; + /* end of hack */ + /* Compensate for border so text doesn't move */ + padding-left: 8px; + padding-right: 23px; +} + GoToWebButton#share_publish_button { background: transparent; border: none; - color: #dd4814; text-decoration: underline; + color: #dd4814; + padding-left: 10px; + padding-right: 25px; + background-image: url(:/external_icon_orange.png); + background-repeat: no-repeat; + background-position: right; + background-origin: margin; +} + +GoToWebButton#share_publish_button:focus { + border: 2px solid #aea79f; + /* hack to make the mild-orange focused box dissapear */ + padding-top: 100%; + padding-bottom: 100%; + /* end of hack */ + /* Compensate for border so text doesn't move */ + padding-left: 8px; + padding-right: 23px; } QTabBar::tab { @@ -192,16 +241,15 @@ border-width: 1px; } -QTabBar::tab:first:!selected { - border-left-color: #939389; +QTabBar::tab:first { border-left-color: #939389; } -QTabBar::tab:first:selected { - border-left-color: #939389; +QTabBar::tab:middle:!selected { + border-left-color: #e4e0dd; } -QTabBar::tab:middle:!selected { +QTabBar::tab:last:!selected { border-left-color: #e4e0dd; } @@ -210,8 +258,12 @@ text-decoration: underline; } -QTabBar::tab:last:!selected { - border-left-color: #e4e0dd; +QTabBar::tab:focus { + text-decoration: underline; + /* hack to make the mild-orange focused box dissapear */ + padding-left: 1000px; + padding-right: 1000px; + /* end of hack */ } QTabWidget { @@ -237,7 +289,7 @@ border: none; margin-top: 1ex; color: #333333; - font: bold 14px; + font-weight: bold; } QGroupBox#profile, @@ -258,13 +310,22 @@ QListWidget#list_devices::item { min-height: 48px; } +QListWidget#list_devices::item:selected { + background: #fcece7; +} + +QGroupBox#local_device_box, +QListWidget#list_devices::item { + padding-left: 9px; + padding-right: 9px; +} QLabel[OverQuota="false"] { color: #333333; } QLabel#other_devices_label { - font: bold 16px; + font-weight: bold; } QLabel#percentage_usage_label { @@ -275,24 +336,11 @@ color: white; } -QLabel#sign_in_label { - font: 16px; -} - -QLabel#email_label, -QLabel#password_label { - font-size: 10px; -} - QLabel[OverQuota="true"], QLabel#warning_label { color: #df2d1f; } -QLabel#welcome_label { - font-size: 20px; -} - QAbstractItemView { border-style: solid; border-color: #898989; @@ -301,3 +349,43 @@ alternate-background-color: #f7f6f5; background: #efedec; } + +QCheckBox { + padding-left: 2px; + padding-right: 2px; +} + +QCheckBox:focus { + border-radius: 5px; + border: 2px solid #dd4814; + /* Compensate for border */ + padding-left: 0px; + padding-right: 0px; +} + +GoToWebButton#edit_profile_button, +GoToWebButton#edit_services_button, +GoToWebButton#get_more_space_button { + padding-left: 8px; + padding-right: 25px; + background-image: url(:/external_icon_white.png); + background-repeat: no-repeat; + background-position: right; + background-origin: margin; +} + +QSpinBox { + padding: 3px; +} + +QSpinBox:focus { + border: 2px solid #dd4814; + border-radius: 5px; + padding: 0px; +} + + +QTreeWidget::item:selected { + background: #fcece7; + color: black; +} diff -Nru ubuntuone-control-panel-2.99.90/data/qt/windows.qss ubuntuone-control-panel-2.99.91/data/qt/windows.qss --- ubuntuone-control-panel-2.99.90/data/qt/windows.qss 1970-01-01 00:00:00.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/data/qt/windows.qss 2012-03-21 14:06:28.000000000 +0000 @@ -0,0 +1,5 @@ +/* Styles specific to the windows platform */ + +QWidget { + font-family: "Ubuntu"; +} diff -Nru ubuntuone-control-panel-2.99.90/debian/changelog ubuntuone-control-panel-2.99.91/debian/changelog --- ubuntuone-control-panel-2.99.90/debian/changelog 2012-03-13 18:39:44.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/debian/changelog 2012-03-21 19:23:31.000000000 +0000 @@ -1,3 +1,44 @@ +ubuntuone-control-panel (2.99.91-0ubuntu1) precise; urgency=low + + * New upstream release: + - Added the app name from ubuntu one to be used in the webclient + so that it is shown in the sso ui (LP: #952880). + - Added 'Computer to cloud' page to the wizard + (part of LP: #933697). + - Tweaked the title of the aforementioned page (LP: #888521). + - Added 'Cloud to computer' page to the initial wizard + (part of LP: #933697). + - Added a confirmation dialog to quit the wizard + (part of LP: #933697). + - Implemented a method to list the user's default folders in + every platform (part of LP: #933697). + - Apply new specification for tabbing navigation and tabbing style +   (LP: #942020). + - Make the "Explore" buttons for folders be disabled (style-wise) +   when the folder is not synched (LP: #949035). + - Removed first line from the man page for the qt control panel + so is detected as such and installed (LP: #948970). + - Added several tweaks to the UI stylesheet to avoid 'movements' + when focusing a button, and to remove the ugly border from the + twitter and facebook buttons. + - Enable platform-specific styling (LP: #953318). + - Arranged Tab ordering in folders tab according to guidelines + (LP: #950073). + - Enabled Qt translation engine so standard dialogs are translated + (LP: #951651). + - Switched to the native file chooser on Linux (LP: #947711). + - Fixed --alert and --switch-to tabname and updated --help and + manpage accordingly (LP: #940465). + - Don't hard-code the font sizes (LP: #942025, LP: #953062). + * Removed patches which were included upstream. + * debian/watch: + - Updated url to fetch tarball from latest milestone. + * debian/control: + - Bumped dependency versions on ubuntu-sso-client and ubuntuone-client to + 2.99.91. + + -- Natalia Bidart (nessita) Wed, 21 Mar 2012 14:11:29 -0300 + ubuntuone-control-panel (2.99.90-0ubuntu2) precise; urgency=low * debian/control: Fix dependencies: python-gobject → python-gi. diff -Nru ubuntuone-control-panel-2.99.90/debian/control ubuntuone-control-panel-2.99.91/debian/control --- ubuntuone-control-panel-2.99.90/debian/control 2012-03-13 18:39:21.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/debian/control 2012-03-21 16:15:57.000000000 +0000 @@ -15,7 +15,7 @@ ${python:Depends}, python, python-ubuntuone-control-panel (= ${binary:Version}), - ubuntuone-client (>= 2.99.90) + ubuntuone-client (>= 2.99.91) Suggests: ubuntuone-control-panel-gui Description: Ubuntu One Control Panel Desktop application to manage an Ubuntu One account. @@ -32,8 +32,8 @@ python-gi, python-simplejson, python-twisted-core, - python-ubuntu-sso-client (>= 2.99.90), - python-ubuntuone-client (>= 2.99.90) + python-ubuntu-sso-client (>= 2.99.91), + python-ubuntuone-client (>= 2.99.91) Description: Ubuntu One Control Panel - Python Libraries Ubuntu One Control Panel provides a Python library to manage an Ubuntu One account. @@ -43,7 +43,7 @@ Breaks: ubuntuone-control-panel-gtk (<< 2.99.5) Replaces: ubuntuone-control-panel-gtk (<< 2.99.5) Depends: ${misc:Depends}, - ubuntuone-installer (>= 2.99.90) + ubuntuone-installer (>= 2.99.91) Recommends: indicator-messages Description: Ubuntu One Control Panel - Common frontend files Common files for the Ubuntu One Control Panel, including shared images used @@ -68,12 +68,12 @@ python-qt4, python-qt4-dbus, python-twisted-core, - python-ubuntu-sso-client (>= 2.99.90), - python-ubuntuone-client (>= 2.99.90), + python-ubuntu-sso-client (>= 2.99.91), + python-ubuntuone-client (>= 2.99.91), python-ubuntuone-control-panel (= ${binary:Version}), - ubuntu-sso-client (>= 2.99.90), - ubuntu-sso-client-qt (>= 2.99.90), - ubuntuone-client (>= 2.99.90), + ubuntu-sso-client (>= 2.99.91), + ubuntu-sso-client-qt (>= 2.99.91), + ubuntuone-client (>= 2.99.91), ubuntuone-control-panel (= ${binary:Version}), ubuntuone-control-panel-common (= ${binary:Version}) Provides: ubuntuone-control-panel-gui diff -Nru ubuntuone-control-panel-2.99.90/debian/patches/fix-948970.patch ubuntuone-control-panel-2.99.91/debian/patches/fix-948970.patch --- ubuntuone-control-panel-2.99.90/debian/patches/fix-948970.patch 2012-03-07 14:29:10.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/debian/patches/fix-948970.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -=== modified file 'docs/ubuntuone-control-panel-qt.1' ---- old/docs/ubuntuone-control-panel-qt.1 2012-03-06 12:26:26 +0000 -+++ new/docs/ubuntuone-control-panel-qt.1 2012-03-07 14:24:34 +0000 -@@ -1,4 +1,3 @@ --.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. - .TH UBUNTUONE-CONTROL-PANEL-QT "1" "March 2012" "ubuntuone-control-panel-qt" "User Commands" - .SH NAME - ubuntuone-control-panel-qt \- control panel for Ubuntu One - diff -Nru ubuntuone-control-panel-2.99.90/debian/patches/series ubuntuone-control-panel-2.99.91/debian/patches/series --- ubuntuone-control-panel-2.99.90/debian/patches/series 2012-03-07 13:53:45.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -fix-948970.patch diff -Nru ubuntuone-control-panel-2.99.90/debian/watch ubuntuone-control-panel-2.99.91/debian/watch --- ubuntuone-control-panel-2.99.90/debian/watch 2012-03-06 18:37:00.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/debian/watch 2012-03-21 14:14:29.000000000 +0000 @@ -1,2 +1,2 @@ version=3 -http://launchpad.net/ubuntuone-control-panel/stable-3-0/2.99.90/ .*/ubuntuone-control-panel-([0-9.]+)\.tar\.gz +http://launchpad.net/ubuntuone-control-panel/stable-3-0/2.99.91/ .*/ubuntuone-control-panel-([0-9.]+)\.tar\.gz diff -Nru ubuntuone-control-panel-2.99.90/docs/ubuntuone-control-panel-qt.1 ubuntuone-control-panel-2.99.91/docs/ubuntuone-control-panel-qt.1 --- ubuntuone-control-panel-2.99.90/docs/ubuntuone-control-panel-qt.1 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/docs/ubuntuone-control-panel-qt.1 2012-03-21 14:06:28.000000000 +0000 @@ -1,4 +1,3 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. .TH UBUNTUONE-CONTROL-PANEL-QT "1" "March 2012" "ubuntuone-control-panel-qt" "User Commands" .SH NAME ubuntuone-control-panel-qt \- control panel for Ubuntu One @@ -13,10 +12,7 @@ .TP \fB\-\-switch\-to\fR PANEL_NAME Start Ubuntu One in the PANEL_NAME tab. Possible -values are: dashboard, volumes, devices, applications -.TP -\fB\-a\fR, \fB\-\-alert\fR -Start Ubuntu One alerting the user to its presence. +values are: folders, devices, settings, account .TP \fB\-\-minimized\fR Start Ubuntu One only in the notification area, with diff -Nru ubuntuone-control-panel-2.99.90/PKG-INFO ubuntuone-control-panel-2.99.91/PKG-INFO --- ubuntuone-control-panel-2.99.90/PKG-INFO 2012-03-06 18:19:14.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/PKG-INFO 2012-03-21 14:06:49.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: ubuntuone-control-panel -Version: 2.99.90 +Version: 2.99.91 Summary: Ubuntu One Control Panel Home-page: https://launchpad.net/ubuntuone-control-panel Author: Natalia Bidart diff -Nru ubuntuone-control-panel-2.99.90/setup.py ubuntuone-control-panel-2.99.91/setup.py --- ubuntuone-control-panel-2.99.90/setup.py 2012-03-06 18:18:51.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/setup.py 2012-03-21 14:06:29.000000000 +0000 @@ -194,7 +194,7 @@ DistUtilsExtra.auto.setup( name='ubuntuone-control-panel', - version='2.99.90', + version='2.99.91', license='GPL v3', author='Natalia Bidart', author_email='natalia.bidart@canonical.com', diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/__init__.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/__init__.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/__init__.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/__init__.py 2012-03-21 14:06:28.000000000 +0000 @@ -18,6 +18,10 @@ import gettext +# pylint: disable=W0611 +from ubuntuone.clientdefs import APP_NAME +# pylint: enable=W0611 + from ubuntuone.controlpanel import TRANSLATION_DOMAIN from ubuntuone.controlpanel.backend import UBUNTUONE_LINK @@ -67,6 +71,7 @@ EDIT_PROFILE_LINK = u'https://login.ubuntu.com/' EDIT_SERVICES_LINK = UBUNTUONE_LINK + u'services' FACEBOOK_LINK = u'http://www.facebook.com/ubuntuone/' +GET_STORAGE_LINK = UBUNTUONE_LINK + u'services/#storage_panel' GET_SUPPORT_LINK = UBUNTUONE_LINK + u'support/' LEARN_MORE_LINK = UBUNTUONE_LINK MANAGE_FILES_LINK = UBUNTUONE_LINK + u'files/' @@ -78,6 +83,20 @@ ACCOUNT_LABEL = _('Your services') ALWAYS_SUBSCRIBED = _('Always in sync') +ARE_YOU_SURE_HELP = _('If you need to know more about Ubuntu One, then ' + 'please go to {support_url}') +ARE_YOU_SURE_NO = _('No, continue setting up') +ARE_YOU_SURE_SUBTITLE = _('You can restart the setup process at any time ' + 'by clicking on Ubuntu One in your menu.') +ARE_YOU_SURE_TITLE = _('Are you sure you want to cancel setting up ' + 'Ubuntu One?') +ARE_YOU_SURE_YES = _('Yes, I want to cancel') +CLOUD_TO_COMPUTER_SUBTITLE = _('These are the folders in your cloud. ' + 'Select the ones you want to sync with this computer.') +CLOUD_TO_COMPUTER_TITLE = _('Syncing the cloud to your computer') +COMPUTER_TO_CLOUD_SUBTITLE = _('Okay! Now it\'s time to choose which folders ' + 'on this computer you would like to sync to the cloud.') +COMPUTER_TO_CLOUD_TITLE = _('Syncing your computer with the cloud') CONNECT_BUTTON_LABEL = _('Connect to Ubuntu One') CONTACTS = _('Thunderbird plug-in') CREDENTIALS_ERROR = _('There was a problem while retrieving the credentials.') @@ -160,8 +179,25 @@ INSTALL_PLUGIN = _('Install the %(plugin_name)s for the sync service: ' '%(service_name)s') INSTALLING = _('Installation of %(package_name)s in progress') +LICENSE_AGREE = _('Agree and continue') +LICENSE_AGREEMENT = _('License Agreement') +LICENSE_BASIC = _('Ubuntu One Basic is free, while additional service add-ons ' + 'may be paid for services.') +LICENSE_DISAGREE = _('Disagree and uninstall') +LICENSE_GPL3 = _('This program is free software: you can redistribute it ' + 'and/or modify it under the terms of the GNU General Public License ' + 'version 3, as published by the Free Software Foundation.') +LICENSE_LINK = _('As free software, this programme is distributed without ' + 'warranty. See the GNU General Public License for more details at ' + '{license_link}') LOADING = _('Loading...') LOADING_OVERLAY = _('Getting information, please wait...') +LOCAL_FOLDERS_CALCULATING = _('Calculating...') +LOCAL_FOLDERS_OVERFLOW = _('The folders you have selected to sync take ' + 'over your {quota_total} space. ' + 'You can remove some folders or add some extra storage.') +LOCAL_FOLDERS_FOLDER_HEADER = _('Sync these folders on my computer') +LOCAL_FOLDERS_SPACE_HEADER = _('Space {space_total}') MAIN_ACCOUNT_TAB = _('Account information') MAIN_DEVICES_TAB = _('Devices') MAIN_FOLDERS_TAB = _('Folders') diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/addfolder.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/addfolder.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/addfolder.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/addfolder.py 2012-03-21 14:06:28.000000000 +0000 @@ -19,6 +19,7 @@ """The user interface for the control panel for Ubuntu One.""" from __future__ import division +import sys from PyQt4 import QtGui, QtCore from twisted.internet import defer @@ -32,6 +33,16 @@ logger = setup_logging('qt.addfolder') CLOSE = QtGui.QMessageBox.Close +# NOTE: this is temporary because of a Qt bug that will be fixed +# on Qt 4.9. You cannot, in general, use sys.platform in these +# modules. Do not do this. Please. This is an exception, one +# time only, pinky-swear, cross my heart and hope to die if +# I lie. +FILE_CHOOSER_OPTIONS = QtGui.QFileDialog.ShowDirsOnly +if sys.platform == 'win32': + FILE_CHOOSER_OPTIONS |= QtGui.QFileDialog.DontUseNativeDialog +# Yes, that is true. If you do that again, you will be hunted +# down and taught a lesson. You will be sorry. class AddFolderButton(cache.Cache, QtGui.QPushButton): @@ -45,6 +56,8 @@ def __init__(self, *args, **kwargs): """Initialize the UI of the widget.""" super(AddFolderButton, self).__init__(*args, **kwargs) + self.add_folder_func = \ + lambda *a, **kw: defer.fail(NotImplementedError()) self.cloud_folders = [] self.clicked.connect(self.on_clicked) @@ -57,20 +70,22 @@ home_dir = yield self.backend.get_home_dir() folder = QtGui.QFileDialog.getExistingDirectory( parent=self, directory=home_dir, - options=QtGui.QFileDialog.DontUseNativeDialog) + options=FILE_CHOOSER_OPTIONS) folder = unicode(QtCore.QDir.toNativeSeparators(folder)) - logger.debug('on_add_folder_button_clicked: user requested folder ' - 'creation for path %r', folder) + logger.info('on_add_folder_button_clicked: user requested folder ' + 'creation for path %r.', folder) if folder == '': self.folderCreationCanceled.emit() - return + defer.returnValue(None) is_valid = yield self.backend.validate_path_for_folder(folder) if not is_valid: + logger.error('on_add_folder_button_clicked: user requested to ' + 'create a folder for an invalid path %r.', folder) text = FOLDER_INVALID_PATH % {'folder_path': folder, 'home_folder': home_dir} QtGui.QMessageBox.warning(self, '', text, CLOSE) - return + defer.returnValue(None) - yield self.backend.create_folder(folder_path=folder) + yield self.add_folder_func(folder_path=folder) self.folderCreated.emit(folder) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/controlpanel.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/controlpanel.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-03-21 14:06:28.000000000 +0000 @@ -24,10 +24,10 @@ from ubuntuone.controlpanel.backend import AUTOCONNECT_KEY from ubuntuone.controlpanel.logger import setup_logging, log_call from ubuntuone.controlpanel.gui import ( - EDIT_SERVICES_LINK, FACEBOOK_LINK, GET_HELP_ONLINE, GET_MORE_STORAGE, + GET_STORAGE_LINK, GET_SUPPORT_LINK, GREETING, humanize, @@ -48,8 +48,8 @@ logger = setup_logging('qt.controlpanel') -NAME_STYLE = '
%s!' -PERCENTAGE_STYLE = '%.0f%%' +NAME_STYLE = u'
%s!' +PERCENTAGE_STYLE = u'%.0f%%' class ControlPanel(UbuntuOneBin): @@ -63,7 +63,7 @@ def _setup(self): """Do some extra setupping for the UI.""" self.ui.get_more_space_button.setText(GET_MORE_STORAGE) - self.ui.get_more_space_button.uri = EDIT_SERVICES_LINK + self.ui.get_more_space_button.uri = GET_STORAGE_LINK self.ui.help_button.setText(GET_HELP_ONLINE) self.ui.help_button.uri = GET_SUPPORT_LINK @@ -100,6 +100,8 @@ @log_call(logger.debug) def on_credentials_found(self): """Credentials are not found or were removed.""" + folders_tab_idx = self.ui.tab_widget.indexOf(self.ui.folders_tab) + self.ui.tab_widget.setCurrentIndex(folders_tab_idx) self.ui.switcher.setCurrentWidget(self.ui.management) self.connect_file_sync() self.is_processing = False @@ -159,10 +161,12 @@ """The facebook button was clicked.""" qt.uri_hook(FACEBOOK_LINK) + @log_call(logger.warning) def on_wizard_rejected(self): """Let clients know that we're done.""" self.finished.emit() + @log_call(logger.info) def on_wizard_finished(self, status): - """Move to controlpanel if wizar ended successfully.""" + """Move to controlpanel if wizard ended successfully.""" self.on_credentials_found() diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/device.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/device.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/device.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/device.py 2012-03-21 14:06:28.000000000 +0000 @@ -34,7 +34,7 @@ handle_errors, pixmap_from_name, ) -from ubuntuone.controlpanel.gui.qt.ui import device_ui, device_remote_ui +from ubuntuone.controlpanel.gui.qt.ui import device_ui from ubuntuone.controlpanel.logger import setup_logging COMPUTER_ICON = "computer" @@ -61,27 +61,35 @@ class DeviceWidget(cache.Cache, QtGui.QWidget): - """The widget for each device in the control panel.""" + """The widget for a local device in the control panel.""" removed = QtCore.pyqtSignal() removeCanceled = QtCore.pyqtSignal() - def __init__(self, device_id, *args, **kwargs): + def __init__(self, device_id=None, *args, **kwargs): """Initialize the UI of the widget.""" super(DeviceWidget, self).__init__(*args, **kwargs) self.ui = device_ui.Ui_Form() self.ui.setupUi(self) - # The following is a hack to avoid having the faked self.ui failing - # with AttributeError in the tests. We need to improve the fake so we - # don't leak this to the production code. - if getattr(self.ui, 'remove_device_button', None) is not None: - self.ui.remove_device_button.setText(REMOVE_BUTTON) - + self.ui.remove_device_button.setText(REMOVE_BUTTON) + self._id = None self.id = device_id + def _get_id(self): + """Return this device's id.""" + return self._id + + def _set_id(self, new_id): + """Set this device's id.""" + self._id = new_id + self.ui.remove_device_button.setEnabled(self._id is not None) + + id = property(fget=_get_id, fset=_set_id) + def update_device_info(self, device_info): """Update the device info.""" + self.id = device_info["device_id"] self.ui.device_name_label.setText(device_info["name"]) icon_name = icon_name_from_type(device_info["type"]) pixmap = pixmap_from_name(icon_name) @@ -102,28 +110,20 @@ else: self.removeCanceled.emit() - -class DeviceRemoteWidget(QtGui.QWidget): - - """Remote Device widget for Devices List.""" - - def __init__(self, device_info): - super(DeviceRemoteWidget, self).__init__() - self.ui = device_remote_ui.Ui_Form() - self.ui.setupUi(self) - - text = device_info["name"] - icon_name = icon_name_from_type(device_info["type"]) - pixmap = pixmap_from_name(icon_name) - self.ui.device_icon_label.setPixmap(pixmap) - self.ui.device_name_label.setText(text) - def text(self): """Return the text displayed in the Widget.""" return self.ui.device_name_label.text() + def clear(self): + """Clear this widget's info.""" + self.id = None + self.ui.device_name_label.setText('') + + +class RemoteDeviceWidget(DeviceWidget): + + """Remote device widget.""" -def get_device_for_list_widget(device_info): - """Return a DeviceRemoteWidget with device proper info.""" - item = DeviceRemoteWidget(device_info) - return item + def __init__(self, *args, **kwargs): + super(RemoteDeviceWidget, self).__init__(*args, **kwargs) + self.ui.remove_device_button.hide() diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/devices.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/devices.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/devices.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/devices.py 2012-03-21 14:06:28.000000000 +0000 @@ -50,7 +50,7 @@ def _setup(self): """Do some extra setupping for the UI.""" super(DevicesPanel, self)._setup() - self.ui.local_device.setTitle(DEVICES_LOCAL_LABEL) + self.ui.local_device_box.setTitle(DEVICES_LOCAL_LABEL) self.ui.other_devices.setTitle(DEVICES_REMOTE_LABEL) self.ui.manage_devices_button.setText(DEVICES_MANAGE_LABEL) self.ui.manage_devices_button.uri = EDIT_DEVICES_LINK @@ -66,7 +66,6 @@ @log_call(logger.debug) def process_info(self, info): """Process and display the devices info.""" - self.clear_device_info(self.ui.local_device_box) self.ui.list_devices.clear() for device_info in info: @@ -76,21 +75,9 @@ def on_local_device_removed(self): """When the local device is removed, clear the box and emit signal.""" - self.clear_device_info(self.ui.local_device_box) + self.ui.local_device.clear() self.localDeviceRemoved.emit() - def clear_device_info(self, box): - """Clear all the device info.""" - children = box.count() - # we need to reverse the index list to remove children because: - # "Items are numbered consecutively from 0. If an item is deleted, - # other items will be renumbered." - # http://doc.qt.nokia.com/latest/qlayout.html#itemAt - for i in reversed(range(children)): - widget = box.itemAt(i).widget() - box.removeWidget(widget) - widget.deleteLater() - def update_device_info(self, device_info): """Update one device.""" if device_info["is_local"]: @@ -100,15 +87,13 @@ def update_local_device(self, device_info): """Update the info for the local device.""" - device_widget = device.DeviceWidget(device_id=device_info['device_id']) - device_widget.update_device_info(device_info) - device_widget.removed.connect(self.on_local_device_removed) - - self.ui.local_device_box.addWidget(device_widget) + self.ui.local_device.update_device_info(device_info) + self.ui.local_device.removed.connect(self.on_local_device_removed) def create_remote_device(self, device_info): """Add a remote device to the list.""" - widget = device.get_device_for_list_widget(device_info) + widget = device.RemoteDeviceWidget() + widget.update_device_info(device_info) item = QtGui.QListWidgetItem() self.ui.list_devices.addItem(item) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/folders.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/folders.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/folders.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/folders.py 2012-03-21 14:06:28.000000000 +0000 @@ -19,10 +19,13 @@ from __future__ import division import os +import Queue +import threading from PyQt4 import QtGui, QtCore from twisted.internet import defer +from ubuntuone.controlpanel.utils import default_folders from ubuntuone.controlpanel.logger import setup_logging, log_call from ubuntuone.controlpanel.gui import ( ALWAYS_SUBSCRIBED, @@ -35,6 +38,12 @@ FOLDERS_COLUMN_SYNC_LOCALLY, FOLDERS_CONFIRM_MERGE, FOLDERS_MANAGE_LABEL, + GET_MORE_STORAGE, + humanize, + LOCAL_FOLDERS_CALCULATING, + LOCAL_FOLDERS_FOLDER_HEADER, + LOCAL_FOLDERS_OVERFLOW, + LOCAL_FOLDERS_SPACE_HEADER, MANAGE_FILES_LINK, MUSIC_ICON_NAME, MUSIC_DISPLAY_NAME, @@ -42,9 +51,13 @@ NAME_NOT_SET, SHARE_ICON_NAME, ) -from ubuntuone.controlpanel.gui.qt import uri_hook, icon_from_name +from ubuntuone.controlpanel.gui.qt import ( + handle_errors, + icon_from_name, + uri_hook, +) from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin -from ubuntuone.controlpanel.gui.qt.ui import folders_ui +from ubuntuone.controlpanel.gui.qt.ui import folders_ui, local_folders_ui logger = setup_logging('qt.folders') @@ -53,6 +66,9 @@ SUBSCRIPTION_COL = 1 EXPLORE_COL = 2 +LOCAL_SUBSCRIPTION_COL = 0 +LOCAL_SPACE_COL = 1 + CANCEL = QtGui.QMessageBox.Cancel CHECKED = QtCore.Qt.Checked CLOSE = QtGui.QMessageBox.Close @@ -61,31 +77,70 @@ YES = QtGui.QMessageBox.Yes +def _process_name(name): + """Tweak 'name' with a translatable music folder name.""" + if name == MUSIC_REAL_PATH: + result = MUSIC_DISPLAY_NAME + else: + result = name + return result + + +class ExploreFolderButton(QtGui.QPushButton): + """A specialized button for the folder listing.""" + + def __init__(self, folder_path, parent=None): + super(ExploreFolderButton, self).__init__(parent=parent) + self.folder_path = folder_path + self.setText(FOLDERS_COLUMN_EXPLORE) + self.clicked.connect(self.on_clicked) + + def on_clicked(self): + """Open the folder_path in the default file manager.""" + uri = unicode(QtCore.QUrl.fromLocalFile(self.folder_path).toString()) + uri_hook(uri) + + class FoldersPanel(UbuntuOneBin): - """The Folders Tab Panel widget""" + """The Folders Tab Panel widget.""" - ui_class = folders_ui logger = logger + remote_folders = False + ui_class = folders_ui + widget_items = {} def _setup(self): """Do some extra setupping for the UI.""" super(FoldersPanel, self)._setup() self.ui.add_folder_button.folderCreated.connect(self.on_folder_created) self.ui.add_folder_button.setText(FOLDERS_BUTTON_ADD_FOLDER) + self.ui.add_folder_button.add_folder_func = self.backend.create_folder - self.ui.folders.headerItem().setText(0, FOLDERS_COLUMN_NAME) - self.ui.folders.headerItem().setText(1, FOLDERS_COLUMN_SYNC_LOCALLY) - self.ui.folders.headerItem().setText(2, FOLDERS_COLUMN_EXPLORE) + self.ui.share_publish_button.setVisible(not self.remote_folders) + self.ui.add_folder_button.setVisible(not self.remote_folders) + self.ui.check_settings_button.setVisible(self.remote_folders) + + self.ui.folders.headerItem().setText(FOLDER_NAME_COL, + FOLDERS_COLUMN_NAME) + self.ui.folders.headerItem().setText(SUBSCRIPTION_COL, + FOLDERS_COLUMN_SYNC_LOCALLY) + self.ui.folders.headerItem().setText(EXPLORE_COL, + FOLDERS_COLUMN_EXPLORE) headers = self.ui.folders.header() headers.setResizeMode(FOLDER_NAME_COL, headers.Stretch) headers.setResizeMode(SUBSCRIPTION_COL, headers.ResizeToContents) headers.setResizeMode(EXPLORE_COL, headers.ResizeToContents) - headers.setStretchLastSection(False) self.ui.share_publish_button.setText(FOLDERS_MANAGE_LABEL) self.ui.share_publish_button.uri = MANAGE_FILES_LINK - icon = icon_from_name('external_icon_orange') - self.ui.share_publish_button.setIcon(icon) + QtGui.QApplication.instance().focusChanged.connect(self.focus_changed) + + def focus_changed(self, old, new): + """When a inner widget is focused, scroll to its item.""" + item = self.widget_items.get(new, None) + if item is not None: + self.ui.folders.scrollToItem(item) + self.ui.folders.setCurrentItem(item) @log_call(logger.info) def on_folder_created(self, new_folder): @@ -101,14 +156,7 @@ info = yield self.backend.volumes_info(with_storage_info=False) self.process_info(info) - def _process_name(self, name): - """Tweak 'name' with a translatable music folder name.""" - if name == MUSIC_REAL_PATH: - result = MUSIC_DISPLAY_NAME - else: - result = name - return result - + @handle_errors(logger=logger) @log_call(logger.debug) def process_info(self, info): """Load folders info into the tree view.""" @@ -117,6 +165,7 @@ # the code would be much more complicated. scrollbar_position = self.ui.folders.verticalScrollBar().value() self.ui.folders.clear() + self.widget_items = {} self.is_processing = False for name, _, volumes in info: # ignore free_bytes @@ -139,6 +188,8 @@ self.ui.folders.addTopLevelItem(item) for volume in volumes: + subscribed = bool(volume[u'subscribed']) + is_root = volume[u'type'] == self.backend.ROOT_TYPE is_share = volume[u'type'] == self.backend.SHARE_TYPE @@ -150,7 +201,7 @@ child.volume_path = volume['path'] child.volume_id = volume['volume_id'] - name = self._process_name(volume[u'display_name']) + name = _process_name(volume[u'display_name']) child.setText(FOLDER_NAME_COL, name) child.setToolTip(FOLDER_NAME_COL, name) child.setToolTip(EXPLORE_COL, FOLDERS_COLUMN_EXPLORE) @@ -162,47 +213,72 @@ icon_name = MUSIC_ICON_NAME icon = icon_from_name(icon_name) + child.icon_obj = icon # hack! + child.setIcon(FOLDER_NAME_COL, icon) + item.addChild(child) if is_root: child.setText(SUBSCRIPTION_COL, ALWAYS_SUBSCRIBED) else: # set check state - if bool(volume[u'subscribed']): - child.setCheckState(SUBSCRIPTION_COL, CHECKED) + # We are using an embedded checkbox instead of the + # item's checkState, because of focus and navigation + # issues. + checkbox = QtGui.QCheckBox(parent=self.ui.folders) + checkbox.setMaximumWidth(24) + self.widget_items[checkbox] = child + if subscribed: + checkbox.setCheckState(CHECKED) else: - child.setCheckState(SUBSCRIPTION_COL, UNCHECKED) + checkbox.setCheckState(UNCHECKED) pixmap = icon.pixmap(24, icon.Disabled, icon.Off) icon = QtGui.QIcon(pixmap) icon.icon_name = icon_name + self.ui.folders.setItemWidget(child, SUBSCRIPTION_COL, + checkbox) - child.icon_obj = icon # hack! - child.setIcon(FOLDER_NAME_COL, icon) - item.addChild(child) + # Operator not preceded by a space + # pylint: disable=C0322 + cb = lambda checked, item=child: \ + self.on_folders_itemChanged(item, SUBSCRIPTION_COL) + # pylint: enable=C0322 + + checkbox.stateChanged.connect(cb) + + if self.remote_folders: + # no explore button when showing only remote folders + continue # attach a third item with a button to explore the folder - model_index = self.ui.folders.indexFromItem(child, EXPLORE_COL) - button = QtGui.QPushButton(parent=self.ui.folders) - button.setFlat(True) - button.setText(FOLDERS_COLUMN_EXPLORE) - button.setObjectName('explore_folder_button') - policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) - button.setSizePolicy(policy) - button.setEnabled(bool(volume[u'subscribed'])) - - # Operator not preceded by a space - # pylint: disable=C0322 - cb = lambda checked, item=child: \ - self.on_folders_itemActivated(item) - # pylint: enable=C0322 - button.clicked.connect(cb) - self.ui.folders.setIndexWidget(model_index, button) + button = ExploreFolderButton(folder_path=child.volume_path, + parent=self.ui.folders) + self.widget_items[button] = child + button.setEnabled(subscribed) + self.ui.folders.setItemWidget(child, EXPLORE_COL, button) self.ui.folders.expandAll() self.ui.folders.verticalScrollBar().setValue(scrollbar_position) + # Rearrange the focus chain so that explore buttons + # and checkboxes are right after ui.folders, and in + # the displayed order. + previous_widget = self.ui.folders + it = QtGui.QTreeWidgetItemIterator(self.ui.folders) + while it.value(): + item = it.value() + checkbox = self.ui.folders.itemWidget(item, SUBSCRIPTION_COL) + button = self.ui.folders.itemWidget(item, EXPLORE_COL) + if checkbox: + QtGui.QWidget.setTabOrder(previous_widget, checkbox) + previous_widget = checkbox + if button: + QtGui.QWidget.setTabOrder(previous_widget, button) + previous_widget = button + it += 1 + # Invalid name "on_folders_itemActivated", "on_folders_itemChanged" # pylint: disable=C0103 + @handle_errors(logger=logger) def on_folders_itemActivated(self, item, column=None): """User activated a given row, open the path in a file browser.""" volume_path = getattr(item, 'volume_path', None) @@ -216,6 +292,7 @@ uri = unicode(QtCore.QUrl.fromLocalFile(volume_path).toString()) uri_hook(uri) + @handle_errors(logger=logger) @defer.inlineCallbacks def on_folders_itemChanged(self, item, column=None): """User changed the subscription for a given folder.""" @@ -230,7 +307,10 @@ # ignore signals when the UI is being updated return - subscribed = item.checkState(SUBSCRIPTION_COL) == CHECKED # new state + checkbox = self.ui.folders.itemWidget(item, SUBSCRIPTION_COL) + if not checkbox: + return + subscribed = checkbox.checkState() == CHECKED # new state logger.info('on_folders_itemChanged: processing volume id %r with ' 'path %r, new subscribed value is: %r. Path exists? %r', volume_id, volume_path, subscribed, @@ -251,6 +331,296 @@ else: # restore old value old = UNCHECKED if subscribed else CHECKED - item.setCheckState(SUBSCRIPTION_COL, old) + checkbox.setCheckState(old) self.is_processing = False + + +class RemoteFoldersPanel(FoldersPanel): + """The Folders Panel that only shows remote cloud folders.""" + + remote_folders = True + + +class CalculateSize(threading.Thread): + """A thread that calculates, in the background, the size of a folder.""" + + def __init__(self, path_name, queue): + self.path_name = path_name + self.queue = queue + self._stop = False + + super(CalculateSize, self).__init__() + + # http://docs.python.org/library/threading.html#threading.Thread.daemon + # "A boolean value indicating whether this thread is a daemon thread + # (True) or not (False)" + self.daemon = True + + def run(self): + """Run this thread.""" + logger.debug('size_calculator: about to calculate size for %r.', + self.path_name) + try: + total_size = 0 + for dirpath, _, filenames in os.walk(self.path_name): + for f in filenames: + fp = os.path.join(dirpath, f) + if os.path.isfile(fp): + total_size += os.path.getsize(fp) + if self._stop: + logger.warning('size_calculator: stopping due to external ' + 'request.') + return + except: # pylint: disable=W0702 + logger.exception('size_calculator: failed for %r:', self.path_name) + else: + logger.info('size_calculator: added new size %r for %r.', + self.path_name, total_size) + self.queue.put([self.path_name, total_size]) + + +class FolderItem(QtGui.QTreeWidgetItem): + """A folder in the local folder list.""" + + def __init__(self, values=None, path=None, queue=None, volume_id=None): + super(FolderItem, self).__init__(values) + self.path = path + self.volume_id = volume_id + self.thread = None + self.size = 0 + + state = UNCHECKED + if path is not None: + if volume_id is not None: + state = CHECKED + elif queue is not None: + # calculate sizes of non-existing folders + self.thread = CalculateSize(path, queue) + self.thread.start() + self.size = None + + self.setCheckState(LOCAL_SUBSCRIPTION_COL, state) + + +class LocalFoldersPanel(UbuntuOneBin): + """The panel that only shows local, non-synched folders.""" + + changesApplied = QtCore.pyqtSignal() + changesCanceled = QtCore.pyqtSignal() + logger = logger + ui_class = local_folders_ui + + def __init__(self, *args, **kwargs): + super(LocalFoldersPanel, self).__init__(*args, **kwargs) + self.queue = Queue.Queue() + self.timer = QtCore.QTimer() + self.items = {} + self.user_home = None + self.account_info = None + + def _setup(self): + """Do some extra setupping for the UI.""" + super(LocalFoldersPanel, self)._setup() + # Start with storage upgrade offer invisible + self.ui.offer_frame.setVisible(False) + + headers = self.ui.folders.header() + headers.setResizeMode(LOCAL_SUBSCRIPTION_COL, headers.Stretch) + headers.setResizeMode(LOCAL_SPACE_COL, headers.Custom) + headers.resizeSection(LOCAL_SPACE_COL, 130) + + self.ui.folders.headerItem().setText(LOCAL_SUBSCRIPTION_COL, + LOCAL_FOLDERS_FOLDER_HEADER) + self._set_space_header() + + self.ui.add_folder_button.folderCreated.connect(self.on_folder_created) + self.ui.add_folder_button.setText(FOLDERS_BUTTON_ADD_FOLDER) + self.ui.add_folder_button.add_folder_func = self.add_folder + + self.ui.add_storage_button.setText(GET_MORE_STORAGE) + + def _set_space_header(self, total=None): + """Set the folders listing 'space' header.""" + if total is None: + total = '' + else: + try: + total = humanize(long(total)) + except (TypeError, ValueError): + pass + total = '(%s)' % total + + title = LOCAL_FOLDERS_SPACE_HEADER.format(space_total=total) + self.ui.folders.headerItem().setText(LOCAL_SPACE_COL, title) + + def _stop(self): + """Stop all pending threads and timers.""" + self.timer.stop() + self.timer = None + + for item in self.items.itervalues(): + if item.thread is None: + logger.warning('LocalFoldersPanel: attempted to stop a thread ' + 'for an item with a None thread.') + else: + item.thread._stop = True + + # pylint: disable=E0202 + @defer.inlineCallbacks + def load(self): + """Load specific tab info.""" + self.is_processing = True + self.account_info = yield self.backend.account_info() + self._set_space_header(self.account_info['quota_used']) + volumes_info = yield self.backend.volumes_info(with_storage_info=False) + yield self.process_info(volumes_info) + + @defer.inlineCallbacks + @log_call(logger.debug) + def process_info(self, volumes_info): + """Load local folders info into the tree view.""" + try: + folders = [] + for _, _, volumes in volumes_info: + for volume in volumes: + if (volume[u'type'] == self.backend.FOLDER_TYPE and + bool(volume['subscribed'])): + folders.append((volume['path'], volume['volume_id'])) + + # add local folders only if they are valid + self.user_home = yield self.backend.get_home_dir() + for folder in default_folders(user_home=self.user_home): + is_valid = yield self.backend.validate_path_for_folder(folder) + if is_valid: + folders.append((folder, None)) + + # always clear the items dict first, since clearing the folders + # list will trigger "underlying C/C++ object has been deleted" + self.items.clear() + self.ui.folders.clear() + + # self.timer can be None if self._stop was called before + # this piece of code was executed + if self.timer is not None: + for path, volume_id in folders: + self.add_folder(folder_path=path, volume_id=volume_id) + + self.timer.start(2000) + self.timer.timeout.connect(self.update_sizes) + finally: + self.is_processing = False + + @handle_errors(logger=logger) + @defer.inlineCallbacks + def apply_changes(self): + """When moving to next page, create/[un]subscribe UDFs.""" + self.is_processing = True + try: + self._stop() + + for path, item in self.items.iteritems(): + subscribed = item.checkState(LOCAL_SUBSCRIPTION_COL) == CHECKED + if item.volume_id is not None: + logger.info('apply_changes: change settings for %r to %r.', + item.path, dict(subscribed=subscribed)) + yield self.backend.change_volume_settings(item.volume_id, + dict(subscribed=subscribed)) + else: + if subscribed: + logger.info('apply_changes: create folder for %r.', + path) + yield self.backend.create_folder(path) + finally: + self.is_processing = False + + self.changesApplied.emit() + + @handle_errors(logger=logger) + def add_folder(self, folder_path, volume_id=None): + """Add a folder to the list.""" + if folder_path in self.items: + logger.warning('LocalFoldersPanel: already have an item for %r.', + folder_path) + + if self.user_home is None: + logger.warning('LocalFoldersPanel: user home is None! ' + 'paths will not be pretty.') + display_name = folder_path + else: + user_home = self.user_home + os.path.sep + display_name = _process_name(folder_path.replace(user_home, '')) + + item = FolderItem([display_name, ""], path=folder_path, + queue=self.queue, volume_id=volume_id) + self.ui.folders.addTopLevelItem(item) + + if volume_id is None: # new folder + self.items[folder_path] = item + + @log_call(logger.info) + def on_folder_created(self, new_folder): + """User clicked on the "Add Folder" button.""" + item = self.items.get(unicode(new_folder)) + if item is not None: + item.setCheckState(LOCAL_SUBSCRIPTION_COL, CHECKED) + else: + logger.warning('LocalFoldersPanel: on_folder_created was called ' + 'for %r which is not tracked in the internal dict', + unicode(new_folder)) + + @handle_errors(logger=logger) + def update_sizes(self): + """Poll the queue were the threads put the size info. + + Every item put in this queue will be a non-synched folder. Thus, the + item's volume_id will be None, so no need to check that. + + """ + while True: + try: + path, size = self.queue.get(block=False) + except Queue.Empty: + break + else: + item = self.items.get(path) + if item: + item.size = size + item.setText(LOCAL_SPACE_COL, humanize(size)) + + total = long(self.account_info['quota_used']) + for path, item in self.items.iteritems(): + if item.size is None: + total = LOCAL_FOLDERS_CALCULATING + break + + subscribed = item.checkState(LOCAL_SUBSCRIPTION_COL) == CHECKED + if subscribed: + total += item.size + + if isinstance(total, long): + self.show_hide_offer(total) + else: + self.show_hide_offer(0) + + self._set_space_header(total) + + def show_hide_offer(self, current_size): + """Show or hide the offer to buy space according to the total size.""" + quota = long(self.account_info['quota_total']) + if current_size > quota: + msg = LOCAL_FOLDERS_OVERFLOW.format(quota_total=humanize(quota)) + self.ui.offer_label.setText(msg) + self.ui.offer_frame.setVisible(True) + else: + self.ui.offer_frame.setVisible(False) + + # pylint: disable=C0103 + + def on_folders_itemChanged(self, item, column): + """Update the size for the chosen row.""" + if column == LOCAL_SUBSCRIPTION_COL: + self.update_sizes() + self.items[item.path] = item + + # pylint: enable=C0103 diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/gotoweb.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/gotoweb.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/gotoweb.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/gotoweb.py 2012-03-21 14:06:28.000000000 +0000 @@ -23,18 +23,25 @@ from twisted.internet import defer from ubuntuone.controlpanel import cache +from ubuntuone.controlpanel.gui import ( + GET_MORE_STORAGE, + GET_STORAGE_LINK, +) from ubuntuone.controlpanel.gui import qt class GoToWebButton(cache.Cache, QtGui.QPushButton): """The GoToWebButton widget""" + uri = None + legend = None + def __init__(self, *args, **kwargs): """Initialize the UI of the widget.""" super(GoToWebButton, self).__init__(*args, **kwargs) - self.uri = None - self.setIcon(qt.icon_from_name('external_icon_white')) - self.setLayoutDirection(QtCore.Qt.RightToLeft) + if self.legend is not None: + self.setText(self.legend) + self.clicked.connect(self.on_clicked) self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) @@ -45,3 +52,10 @@ if self.uri is not None: uri = yield self.backend.build_signed_iri(self.uri) qt.uri_hook(uri) + + +class GetStorageButton(GoToWebButton): + """A specific GoToWebButton to get more storage.""" + + legend = GET_MORE_STORAGE + uri = GET_STORAGE_LINK diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/gui.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/gui.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/gui.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/gui.py 2012-03-21 14:06:28.000000000 +0000 @@ -21,6 +21,16 @@ from ubuntuone.controlpanel.gui.qt.systray import TrayIcon from ubuntuone.controlpanel.gui.qt.ui import mainwindow_ui +# pylint: disable=E0611 +try: + from gi.repository import Unity + USE_LIBUNITY = True +except ImportError: + USE_LIBUNITY = False +# pylint: enable=E0611 + +U1_DOTDESKTOP = "ubuntuone-installer.desktop" + class MainWindow(QtGui.QMainWindow): """The Main Window of the Control Panel.""" @@ -36,12 +46,23 @@ triggered=self.close) self.quit_action.setShortcuts(["Ctrl+q", "Ctrl+w"]) self.addAction(self.quit_action) + if USE_LIBUNITY: + self.entry = Unity.LauncherEntry.get_for_desktop_id(U1_DOTDESKTOP) + else: + self.entry = None def _setup(self): """Do some extra setupping for the UI.""" self.ui.control_panel.finished.connect(self.close) - # Invalid name "closeEvent" + def switch_to(self, tabname="folders"): + """Switch control panel to the required tab.""" + tabnames = ["folders", "devices", "settings", "account"] + if tabname in tabnames: + self.ui.control_panel.ui.tab_widget.setCurrentIndex( + tabnames.index(tabname)) + + # Invalid names "closeEvent" "focusInEvent" # pylint: disable=C0103 def closeEvent(self, event): @@ -50,6 +71,16 @@ self.close_callback() event.accept() + def set_urgent(self, value): + """Set the urgent attribute in the launcher to value.""" + if self.entry: + self.entry.set_property('urgent', value) + + def focusInEvent(self, event): + """The main window got focus, remove urgent bit.""" + self.set_urgent(False) + return super(MainWindow, self).focusInEvent(event) + # pylint: enable=C0103 diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/loadingoverlay.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/loadingoverlay.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/loadingoverlay.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/loadingoverlay.py 2012-03-21 14:06:28.000000000 +0000 @@ -23,6 +23,8 @@ from ubuntuone.controlpanel.gui import LOADING_OVERLAY from ubuntuone.controlpanel.gui.qt.ui import loadingoverlay_ui +LOADING_OVERLAY_MARKUP = u'{0}' + class LoadingOverlay(QtGui.QFrame): """The widget that shows a loading animation and disable the widget below. @@ -45,11 +47,7 @@ self.counter = 0 self.orientation = False - # The following is a hack to avoid having the faked self.ui failing - # with AttributeError in the tests. We need to improve the fake so we - # don't leak this to the production code. - if getattr(self.ui, 'label', None) is not None: - self.ui.label.setText(LOADING_OVERLAY) + self.ui.label.setText(LOADING_OVERLAY_MARKUP.format(LOADING_OVERLAY)) # Invalid name "paintEvent" # pylint: disable=C0103 diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/main/__init__.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/main/__init__.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-03-21 14:06:28.000000000 +0000 @@ -45,11 +45,7 @@ metavar="PANEL_NAME", default="", help="Start Ubuntu One in the " "PANEL_NAME tab. Possible values are: " - "dashboard, volumes, devices, applications") - result.add_argument("-a", "--alert", dest="alert", action="store_true", - default=False, - help="Start Ubuntu One " - "alerting the user to its presence.") + "folders, devices, settings, account") result.add_argument("--minimized", dest="minimized", action="store_true", default=False, help="Start Ubuntu One " @@ -67,23 +63,37 @@ # The following cannot be imported outside this function # because u1trial already provides a reactor. - args = ['ubuntuone-installer'] + args[1:] + args = ['ubuntuone-installer'] + args app = UniqueApplication(args, "ubuntuone-control-panel") + + # Install translator for standard dialogs. + locale = unicode(QtCore.QLocale.system().name()) + translator = QtCore.QTranslator() + translator.load("qt_" + locale, + QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)) + app.installTranslator(translator) + parser = parser_options() # Use only the arguments that are not recognized by Qt - args = parser.parse_args(args=[unicode(x) for x in app.arguments()[1:]]) - _ = args.switch_to - _ = args.alert + # and after the name of the binary (bug #956143) + arg_list = [unicode(arg) for arg in app.arguments()] + bin_position = arg_list.index(sys.argv[0]) + 1 + args = parser.parse_args(args=arg_list[bin_position:]) + switch_to = args.switch_to minimized = args.minimized with_icon = args.with_icon source.main(app) - qss = QtCore.QResource(":/ubuntuone.qss") - app.setStyleSheet(qss.data()) + data = [] + for qss_name in (source.PLATFORM_QSS, ":/ubuntuone.qss"): + qss = QtCore.QResource(qss_name) + data.append(unicode(qss.data())) + app.setStyleSheet('\n'.join(data)) # Unused variable 'window', 'icon', pylint: disable=W0612 icon, window = start(lambda: source.main_quit(app), minimized=minimized, with_icon=with_icon) + window.switch_to(switch_to) # pylint: enable=W0612 if icon: app.new_instance.connect(icon.restore_window) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/main/linux.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/main/linux.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/main/linux.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/main/linux.py 2012-03-21 14:06:28.000000000 +0000 @@ -31,3 +31,5 @@ def main_quit(app): """Stop the mainloop.""" app.exit() + +PLATFORM_QSS = ":/linux.qss" diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/main/tests/test_main.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/main/tests/test_main.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/main/tests/test_main.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/main/tests/test_main.py 2012-03-21 14:06:28.000000000 +0000 @@ -16,12 +16,26 @@ """Tests for the control panel's Qt main.""" +import sys + +from PyQt4 import QtCore from twisted.internet.defer import inlineCallbacks from ubuntuone.controlpanel.gui.qt import main from ubuntuone.controlpanel.tests import TestCase +class FakeTranslator(object): + + """A fake QTranslator.""" + + args = None + + def load(self, *args, **kwargs): + """fake load.""" + self.args = (args, kwargs) + + class FakeApplication(object): """A fake application.""" @@ -29,6 +43,7 @@ def __init__(self): self.args = None self.style = None + self.translator = None def __call__(self, argv, *args, **kwargs): """Fake arg filtering function.""" @@ -41,6 +56,10 @@ def setStyleSheet(self, *args, **kwargs): """Fake setStyleSheet.""" self.style = (args, kwargs) + + def installTranslator(self, translator): + """Fake installTranslator.""" + self.translator = translator # pylint: enable=C0103 def arguments(self): @@ -52,16 +71,34 @@ pass +class FakeMainWindow(object): + + """A fake MainWindow.""" + + tabname = None + urgent = None + + def switch_to(self, tabname): + """Fake switch_to.""" + self.tabname = tabname + + def set_urgent(self, value): + """Fake set_urgent.""" + self.urgent = value + + class FakeStart(object): """Fake start function.""" def __init__(self): self.args = None + self.window = None def __call__(self, *args, **kwargs): self.args = (args, kwargs) - return None, None + self.window = FakeMainWindow() + return None, self.window class MainTestCase(TestCase): @@ -72,34 +109,62 @@ def setUp(self): yield super(MainTestCase, self).setUp() self.app = FakeApplication() + self.translator = FakeTranslator() self.start = FakeStart() self.patch(main, "UniqueApplication", self.app) self.patch(main, "start", self.start) + self.patch(main.source, "main_start", lambda app: None) + self.patch(QtCore, "QTranslator", lambda: self.translator) def test_wm_class(self): """Test that we set the 1st argument, used for WM_CLASS, correctly.""" - main.main([]) + main.main([sys.argv[0]]) self.assertEqual(self.app.args, - (['ubuntuone-installer'], ('ubuntuone-control-panel',), {})) + (['ubuntuone-installer', sys.argv[0]], + ('ubuntuone-control-panel',), {})) def test_title_not_fail(self): - """Ensure -title is removed before it gets to OptParser.""" - main.main(["blah", "-title"]) + """Ensure -title is removed before it gets to argparse.""" + main.main([sys.argv[0], "-title"]) # Did not crash! + def test_truncate_argv(self): + """Ensure the binary name is not given to argparse.""" + main.main(["foo", "bar", sys.argv[0], "--minimized"]) + self.assertEqual(self.start.args[1], + {'minimized': True, 'with_icon': False}) + def test_minimized_option(self): """Ensure the --minimized option is parsed and passed correctly.""" - main.main(["blah", "--minimized"]) + main.main([sys.argv[0], "--minimized"]) self.assertEqual(self.start.args[1], {'minimized': True, 'with_icon': False}) def test_with_icon_option(self): """Ensure the --minimized option is parsed and passed correctly.""" - main.main(["blah", "--with-icon"]) + main.main([sys.argv[0], "--with-icon"]) self.assertEqual(self.start.args[1], {'minimized': False, 'with_icon': True}) - def test_style_loads(self): - """Ensure the stylesheet is loaded.""" - main.main([]) - self.assertTrue(self.app.style) + def test_all_styles_load(self): + """Ensure the platform style is loaded.""" + main.main([sys.argv[0]]) + data = [] + for qss_name in (main.source.PLATFORM_QSS, ":/ubuntuone.qss"): + qss = QtCore.QResource(qss_name) + data.append(unicode(qss.data())) + self.assertEqual((('\n'.join(data),), {}), self.app.style) + + def test_switch_to_option(self): + """Ensure the --switch-to option is parsed and passed correctly.""" + main.main([sys.argv[0], "--switch-to", "folders"]) + self.assertEqual(self.start.window.tabname, "folders") + + def test_translator(self): + """Ensure the Qt translator is loaded.""" + main.main([sys.argv[0]]) + locale = unicode(QtCore.QLocale.system().name()) + self.assertEqual(self.app.translator, self.translator) + self.assertEqual(self.translator.args, (("qt_" + locale, + QtCore.QLibraryInfo.location( + QtCore.QLibraryInfo.TranslationsPath)), {})) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/main/windows.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/main/windows.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/main/windows.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/main/windows.py 2012-03-21 14:06:28.000000000 +0000 @@ -42,3 +42,5 @@ # pylint: enable=E1101 + +PLATFORM_QSS = ":/windows.qss" diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/signin.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/signin.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/signin.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/signin.py 2012-03-21 14:06:28.000000000 +0000 @@ -24,6 +24,8 @@ from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin from ubuntuone.controlpanel.gui.qt.ui import signin_ui +WELCOME_MARKUP = u'{0}' + class SignInPanel(UbuntuOneBin): """The widget for signing in.""" @@ -33,6 +35,6 @@ def _setup(self): """Do some extra setupping for the UI.""" super(SignInPanel, self)._setup() - self.ui.welcome_label.setText(WELCOME_LABEL) + self.ui.welcome_label.setText(WELCOME_MARKUP.format(WELCOME_LABEL)) self.ui.login_button.setText(EXISTING_ACCOUNT_CHOICE_BUTTON) self.ui.register_button.setText(SET_UP_ACCOUNT_CHOICE_BUTTON) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/__init__.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/__init__.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-03-21 14:06:28.000000000 +0000 @@ -96,6 +96,7 @@ """A fake Ui object.""" exposed_methods = ['setupUi'] + raise_attr_error = False class FakedControlPanelBackend(FakedObject): diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_account.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_account.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_account.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_account.py 2012-03-21 14:06:28.000000000 +0000 @@ -62,10 +62,10 @@ def test_process_info(self): """The info is processed when ready.""" self.ui.process_info(SAMPLE_ACCOUNT_INFO) - self.assertEqual(self.ui.ui.name_label.text(), SAMPLE_NAME) - self.assertEqual(self.ui.ui.email_label.text(), SAMPLE_EMAIL) - self.assertEqual(self.ui.ui.services_description_label.text(), - SAMPLE_PLAN) + self.assertEqual(unicode(self.ui.ui.name_label.text()), SAMPLE_NAME) + self.assertEqual(unicode(self.ui.ui.email_label.text()), SAMPLE_EMAIL) + self.assertEqual(unicode( + self.ui.ui.services_description_label.text()), SAMPLE_PLAN) def test_edit_account_button(self): """When clicking the edit account button, the proper url is opened.""" diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py 2012-03-21 14:06:28.000000000 +0000 @@ -18,8 +18,6 @@ """Tests for the AddFolderButton widget.""" -import os - from twisted.internet import defer from ubuntuone.controlpanel.gui.tests import ( @@ -48,11 +46,10 @@ @defer.inlineCallbacks def setUp(self): yield super(AddFolderButtonTestCase, self).setUp() + self.created_folders = [] self.patch(FakedFileDialog, 'response', gui.QtCore.QString('')) self.patch(self.ui.backend, 'validate_path_for_folder', lambda p: True) - old_home = os.environ['HOME'] - os.environ['HOME'] = USER_HOME - self.addCleanup(lambda: os.environ.__setitem__('HOME', old_home)) + self.patch(self.ui, 'add_folder_func', self.add_folder) @defer.inlineCallbacks def assert_does_nothing(self, method_call): @@ -73,6 +70,18 @@ # the folderCreated signal was not emitted self.assertEqual(self._called, False) + def add_folder(self, folder_path): + """A dummy add folder func.""" + self.created_folders.append(folder_path) + return defer.succeed(None) + + def test_default_add_folder_func(self): + """The add_folder_func is used.""" + folder = set_path_on_file_dialog() + yield self.ui.on_clicked() + + self.assertEqual(self.created_folders, [folder]) + @defer.inlineCallbacks def test_add_folder_button_clicked_opens_file_chooser(self): """When adding a new folder, the proper file chooser is raised.""" @@ -81,7 +90,7 @@ home_dir = yield self.ui.backend.get_home_dir() self.assertEqual(FakedFileDialog.args, ()) self.assertEqual(FakedFileDialog.kwargs, { - 'options': gui.QtGui.QFileDialog.DontUseNativeDialog, + 'options': gui.FILE_CHOOSER_OPTIONS, 'directory': home_dir, 'parent': self.ui, }) @@ -121,7 +130,7 @@ self.assertEqual(FakedDialog.args, None) self.assertEqual(FakedDialog.kwargs, None) # backend called - self.assert_backend_called('create_folder', folder_path=folder) + self.assertEqual(self.created_folders, [folder]) @defer.inlineCallbacks def test_calls_backend(self): @@ -133,7 +142,7 @@ self.assertEqual(FakedDialog.args, None) self.assertEqual(FakedDialog.kwargs, None) # backend called - self.assert_backend_called('create_folder', folder_path=folder) + self.assertEqual(self.created_folders, [folder]) @defer.inlineCallbacks def test_emit_folder_created_on_success(self): diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py 2012-03-21 14:06:28.000000000 +0000 @@ -107,7 +107,7 @@ name = gui.NAME_STYLE % SAMPLE_NAME greeting = gui.GREETING % {'user_display_name': name} - self.assertEqual(self.ui.ui.greeting_label.text(), greeting) + self.assertEqual(unicode(self.ui.ui.greeting_label.text()), greeting) used = int(info['quota_used']) total = int(info['quota_total']) @@ -116,9 +116,10 @@ expected = {'used': gui.humanize(used), 'total': gui.humanize(total)} msg = gui.USAGE_LABEL % expected - self.assertEqual(self.ui.ui.quota_usage_label.text(), msg) + self.assertEqual(unicode(self.ui.ui.quota_usage_label.text()), msg) msg = gui.PERCENTAGE_LABEL % percentage_usage - self.assertEqual(self.ui.ui.percentage_usage_label.text(), msg) + self.assertEqual(unicode(self.ui.ui.percentage_usage_label.text()), + msg) def test_update_over_quota(self): """Check the labels state when the user exceed the quota.""" @@ -180,6 +181,11 @@ self.assertNotIn('connect_files', self.ui.backend._called) + def test_folder_panel_shows_all_folders(self): + """The FolderPanel shows all folders (not remote only).""" + remote = self.ui.ui.folders_tab.remote_folders + self.assertFalse(remote) + class ExternalLinkButtonsTestCase(ControlPanelTestCase): """The link in the go-to-web buttons are correct.""" @@ -187,7 +193,7 @@ def test_get_more_space_button(self): """When clicking the get more GB button, the proper url is opened.""" self.assert_uri_hook_called(self.ui.ui.get_more_space_button, - gui.EDIT_SERVICES_LINK) + gui.GET_STORAGE_LINK) def test_help_button(self): """When clicking the help button, the proper url is opened.""" diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_device.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_device.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_device.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_device.py 2012-03-21 14:06:28.000000000 +0000 @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- - -# Author: Alejandro J. Cura # -# Copyright 2011 Canonical Ltd. +# Copyright 2011-2012 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published @@ -36,7 +34,7 @@ class DeviceWidgetTestCase(BaseTestCase): - """Test the qt control panel.""" + """Test the DeviceWidget class.""" innerclass_ui = gui.device_ui innerclass_name = "Ui_Form" @@ -46,15 +44,35 @@ logger = gui.logger def test_has_id(self): - """The device as an id, None by default.""" + """The device as an id accepted as creation param.""" self.assertEqual(self.ui.id, self.device_id) + def test_id_can_be_none(self): + """The device id is None by default.""" + ui = self.class_ui() + self.assertEqual(ui.id, None) + + def test_setting_id_to_none_disables_remove_button(self): + """If the id is set to None, the remove button is disabled.""" + self.ui.id = None + self.assertFalse(self.ui.ui.remove_device_button.isEnabled()) + + def test_setting_id_to_not_none_enables_remove_button(self): + """If the id is set to not None, the remove button is enabled.""" + self.ui.id = 'not None' + self.assertTrue(self.ui.ui.remove_device_button.isEnabled()) + + def test_remove_button(self): + """The remove button is visible.""" + self.assertTrue(self.ui.ui.remove_device_button.isVisible()) + def test_update_device_info(self): """The widget is updated with the info.""" info = SAMPLE_COMPUTER_INFO expected_name = info["name"] self.ui.update_device_info(info) - self.assertEqual(self.ui.ui.device_name_label.text(), expected_name) + self.assertEqual(unicode(self.ui.ui.device_name_label.text()), + expected_name) # pylint: disable=C0103 def assertIconMatchesType(self, device_type, base_icon_name): @@ -68,7 +86,7 @@ self.assertIconMatchesType(gui.DEVICE_TYPE_PHONE, gui.PHONE_ICON) self.assertIconMatchesType("other random type", gui.COMPUTER_ICON) - def _test_update_device_info_sets_right_icon(self, info): + def assert_update_device_info_sets_right_icon(self, info): """The widget is updated with the right icon.""" self.ui.update_device_info(info) pixmap_name = gui.icon_name_from_type(info["type"]) @@ -78,21 +96,11 @@ def test_update_device_info_sets_computer_icon(self): """The computer icon is set.""" - self._test_update_device_info_sets_right_icon(SAMPLE_COMPUTER_INFO) + self.assert_update_device_info_sets_right_icon(SAMPLE_COMPUTER_INFO) def test_update_device_info_sets_phone_icon(self): """The phone icon is set.""" - self._test_update_device_info_sets_right_icon(SAMPLE_PHONE_INFO) - - def test_get_device_for_list_widget(self): - """The the item list values returned.""" - info = SAMPLE_COMPUTER_INFO - item = gui.get_device_for_list_widget(info) - self.assertEqual(item.text(), info["name"]) - - info = SAMPLE_PHONE_INFO - item = gui.get_device_for_list_widget(info) - self.assertEqual(item.text(), info["name"]) + self.assert_update_device_info_sets_right_icon(SAMPLE_PHONE_INFO) class RemoveDeviceTestCase(DeviceWidgetTestCase): @@ -161,3 +169,13 @@ yield self.ui.ui.remove_device_button.click() self.assertTrue(self.memento.check_exception(CrashyBackendException)) + + +class RemoteDeviceWidgetTestCase(DeviceWidgetTestCase): + """Test the RemoteDeviceWidget class.""" + + class_ui = gui.RemoteDeviceWidget + + def test_remove_button(self): + """The remove button is hidden.""" + self.assertFalse(self.ui.ui.remove_device_button.isVisible()) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_devices.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_devices.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_devices.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_devices.py 2012-03-21 14:06:28.000000000 +0000 @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- - -# Author: Alejandro J. Cura # -# Copyright 2011 Canonical Ltd. +# Copyright 2011-2012 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published @@ -63,7 +61,6 @@ def test_no_devices_at_startup(self): """The UI is reset at startup.""" self.assertEqual(self.ui.ui.list_devices.count(), 0) - self.assertEqual(self.ui.ui.local_device_box.count(), 0) def test_process_info(self): """The widget is updated with the info.""" @@ -71,10 +68,8 @@ local, remote = SAMPLE_DEVICES_INFO[0], SAMPLE_DEVICES_INFO[1:] - self.assertEqual(self.ui.ui.local_device_box.count(), 1) - local_device = self.ui.ui.local_device_box.itemAt(0).widget() - self.assertEqual(local_device.ui.device_name_label.text(), - local['name']) + local_device = self.ui.ui.local_device + self.assertEqual(unicode(local_device.text()), local['name']) self.assertEqual(local_device.id, local['device_id']) self.assertEqual(self.ui.ui.list_devices.count(), @@ -82,28 +77,14 @@ for i, remote_device in enumerate(remote): item = self.ui.ui.list_devices.item(i) device = self.ui.ui.list_devices.itemWidget(item) - self.assertEqual(device.text(), remote_device['name']) + self.assertEqual(unicode(device.text()), remote_device['name']) - def test_remove_device_and_check_layout_state(self): - """Test if the widget is properly removed.""" + def test_local_device(self): + """Test if the local_device widget is properly packed.""" self.ui.process_info(SAMPLE_DEVICES_INFO) - self.ui.show() - self.assertEqual(self.ui.ui.local_device_box.count(), 1) - local_device = self.ui.ui.local_device_box.itemAt(0).widget() - self.executed = False - - def delete_later(reference=None): - """Fake delete later.""" - self.executed = True - self.patch(local_device, "deleteLater", delete_later) - self.ui.clear_device_info(self.ui.ui.local_device_box) - self.ui.process_info(SAMPLE_DEVICES_INFO) - self.assertEqual(self.ui.ui.local_device_box.count(), 1) - local_device2 = self.ui.ui.local_device_box.itemAt(0).widget() - self.assertNotEqual(local_device, local_device2) - self.assertTrue(self.executed) - self.assertFalse(local_device.isVisible()) + local_device = self.ui.ui.local_device_layout.itemAt(0).widget() + self.assertIs(local_device, self.ui.ui.local_device) def test_process_info_twice(self): """The widget is updated with the info.""" @@ -115,21 +96,20 @@ self.assert_uri_hook_called(self.ui.ui.manage_devices_button, gui.EDIT_DEVICES_LINK) - def test_remove_device_widget_after_removal(self): - """When a device widget was deleted, remove it from the UI.""" + def test_local_device_removed_clears_the_widget(self): + """When the local device was deleted, clear it.""" self.ui.process_info(SAMPLE_DEVICES_INFO) - local_device = self.ui.ui.local_device_box.itemAt(0).widget() - local_device.removed.emit() + self.ui.ui.local_device.removed.emit() - self.assertTrue(self.ui.ui.local_device_box.itemAt(0) is None) + self.assertEqual(unicode(self.ui.ui.local_device.text()), '') + self.assertEqual(self.ui.ui.local_device.id, None) def test_local_device_removed_signal(self): """When the local device is removed, emit localDeviceRemoved signal.""" self.ui.localDeviceRemoved.connect(self._set_called) self.ui.process_info(SAMPLE_DEVICES_INFO) - local_device = self.ui.ui.local_device_box.itemAt(0).widget() - local_device.removed.emit() + self.ui.ui.local_device.removed.emit() self.assertEqual(self._called, ((), {})) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py 2012-03-21 14:06:28.000000000 +0000 @@ -60,7 +60,7 @@ status = {backend.STATUS_KEY: status_bd, backend.MSG_KEY: msg_bd} self.ui.process_info(status) - actual_text = self.ui.ui.sync_status_label.text() + actual_text = unicode(self.ui.ui.sync_status_label.text()) self.assertEqual(expected_text, actual_text) actual_icon = self.ui.ui.sync_status_icon.pixmap() @@ -69,7 +69,8 @@ self.assertEqualPixmaps(expected_icon, actual_icon) self.assertTrue(self.ui.ui.sync_status_button.isEnabled()) - self.assertEqual(self.ui.ui.sync_status_button.text(), action) + self.assertEqual(unicode(self.ui.ui.sync_status_button.text()), + action) is_default = self.ui.ui.sync_status_button.isDefault() expected_default = (action == gui.FILE_SYNC_CONNECT) @@ -180,9 +181,10 @@ self.assertFalse(self.ui.ui.sync_status_button.isEnabled()) self.assertFalse(self.ui.ui.sync_status_button.isDefault()) - actual_text = self.ui.ui.sync_status_label.text() + actual_text = unicode(self.ui.ui.sync_status_label.text()) self.assertEqual(actual_text, LOADING) - self.assertEqual(self.ui.ui.sync_status_button.text(), PLEASE_WAIT) + self.assertEqual(unicode(self.ui.ui.sync_status_button.text()), + PLEASE_WAIT) actual_icon = self.ui.ui.sync_status_icon.pixmap() expected_icon = gui.pixmap_from_name('sync_status_loading') self.assertEqualPixmaps(expected_icon, actual_icon) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_folders.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_folders.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2012-03-21 14:06:28.000000000 +0000 @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- - -# Authors: Natalia B Bidart # -# Copyright 2011 Canonical Ltd. +# Copyright 2011-2012 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published @@ -18,13 +16,18 @@ """Tests for the Control Panel.""" +import copy import logging import operator import os +import Queue +import shutil +from PyQt4 import QtGui from twisted.internet import defer from ubuntuone.devtools.handlers import MementoHandler +from ubuntuone.controlpanel.tests import helper_fail from ubuntuone.controlpanel.gui.tests import ( FAKE_VOLUMES_INFO, FAKE_VOLUMES_MINIMAL_INFO, @@ -33,18 +36,25 @@ ) from ubuntuone.controlpanel.gui.qt import folders as gui from ubuntuone.controlpanel.gui.qt.tests import ( + BaseTestCase, FakedDialog, ) from ubuntuone.controlpanel.gui.qt.tests.test_ubuntuonebin import ( UbuntuOneBinTestCase, ) - # Access to a protected member # Instance of 'ControlBackend' has no '_called' member # pylint: disable=W0212, E1103 +def volumes_with_music_unsubscribed(): + """Return a copy of FAKE_VOLUMES_MINIMAL_INFO with music unsubscribed.""" + volumes = copy.deepcopy(FAKE_VOLUMES_MINIMAL_INFO) + volumes[0][2][1][u'subscribed'] = u'' + return volumes + + def _build_name(name): """Helper to build the name expected when showing folder info.""" if name: @@ -54,6 +64,27 @@ return name +class ExploreFolderButtonTestCase(BaseTestCase): + """Test the ExploreFolderButton widget.""" + + class_ui = gui.ExploreFolderButton + kwargs = dict(folder_path=u'foo') + + def test_text(self): + """The button text is correct.""" + self.assertEqual(unicode(self.ui.text()), + gui.FOLDERS_COLUMN_EXPLORE) + + def test_clicked(self): + """Clicking the button opens the folder in the default file manager.""" + self.patch(gui, 'uri_hook', self._set_called) + self.ui.click() + + url = gui.QtCore.QUrl.fromLocalFile(self.kwargs['folder_path']) + expected = unicode(url.toString()) + self.assertEqual(self._called, ((expected,), {})) + + class FoldersPanelTestCase(UbuntuOneBinTestCase): """Test the qt cloud folders tab.""" @@ -71,9 +102,15 @@ self.memento.setLevel(logging.DEBUG) gui.logger.addHandler(self.memento) - old_home = os.environ['HOME'] - os.environ['HOME'] = USER_HOME - self.addCleanup(lambda: os.environ.__setitem__('HOME', old_home)) + def set_item_checked(self, item, checked=True): + """Make item to be checked.""" + checkbox = self.ui.ui.folders.itemWidget(item, gui.SUBSCRIPTION_COL) + checkbox.setCheckState(gui.CHECKED if checked else gui.UNCHECKED) + + def get_item_checked(self, item): + """Get if item is checked.""" + checkbox = self.ui.ui.folders.itemWidget(item, gui.SUBSCRIPTION_COL) + return (checkbox.checkState() == gui.CHECKED) class FoldersPanelVolumesInfoTestCase(FoldersPanelTestCase): @@ -86,10 +123,48 @@ def assert_folder_group_header_correct(self, item, name): """Check that the folder group header is correct.""" - self.assertEqual(item.text(gui.FOLDER_NAME_COL), name) - self.assertEqual(item.text(gui.SUBSCRIPTION_COL), + self.assertEqual(unicode(item.text(gui.FOLDER_NAME_COL)), name) + self.assertEqual(unicode(item.text(gui.SUBSCRIPTION_COL)), gui.FOLDERS_COLUMN_SYNC_LOCALLY) - self.assertEqual(item.text(gui.EXPLORE_COL), '') + if not self.ui.remote_folders: + self.assertEqual(unicode(item.text(gui.EXPLORE_COL)), '') + + def assert_folder_row_correct(self, item, label, icon_name, volume, + tweaked_path=None): + """Check that the folder row 'item' is correct.""" + folders = self.ui.ui.folders + + actual_label = unicode(item.text(gui.FOLDER_NAME_COL)) + self.assertEqual(label, actual_label) + + if volume['type'] == self.ui.backend.ROOT_TYPE: + # no check box but the ALWAYS_SUBSCRIBED legend + self.assertEqual(unicode(item.text(gui.SUBSCRIPTION_COL)), + gui.ALWAYS_SUBSCRIBED) + else: + subscribed = self.get_item_checked(item) + self.assertEqual(subscribed, bool(volume['subscribed'])) + + actual_icon_name = item.icon_obj.icon_name + self.assertEqual(icon_name, actual_icon_name) + + self.assertEqual(item.volume_id, volume['volume_id']) + + expected_path = volume['path'] + if tweaked_path is not None: + expected_path = tweaked_path + self.assertEqual(item.volume_path, expected_path) + + # tooltips are correct + self.assertEqual(item.toolTip(gui.FOLDER_NAME_COL), label) + self.assertEqual(item.toolTip(gui.EXPLORE_COL), + gui.FOLDERS_COLUMN_EXPLORE) + + if not self.ui.remote_folders: + # explore button is in place + model_index = folders.indexFromItem(item, gui.EXPLORE_COL) + button = folders.indexWidget(model_index) + self.assertEqual(button.isEnabled(), bool(volume['subscribed'])) @defer.inlineCallbacks def test_is_processing_while_asking_info(self): @@ -131,7 +206,6 @@ self.assert_folder_group_header_correct(item, name) - # check children self.assertEqual(len(volumes), item.childCount()) sorted_vols = sorted(volumes, key=operator.itemgetter('path')) for volume in sorted_vols: @@ -144,38 +218,14 @@ if volume['type'] == self.ui.backend.SHARE_TYPE: name = volume['name'] expected_path = volume['realpath'] - label = item.text(gui.FOLDER_NAME_COL) - self.assertEqual(label, name) - if volume['type'] == self.ui.backend.ROOT_TYPE: - # no check box but the ALWAYS_SUBSCRIBED legend - self.assertEqual(item.text(gui.SUBSCRIPTION_COL), - gui.ALWAYS_SUBSCRIBED) - else: - subscribed = item.checkState(gui.SUBSCRIPTION_COL) == \ - gui.CHECKED - self.assertEqual(subscribed, bool(volume['subscribed'])) - - icon_name = item.icon_obj.icon_name if volume['type'] != self.ui.backend.SHARE_TYPE: - self.assertEqual(icon_name, gui.FOLDER_ICON_NAME) + icon_name = gui.FOLDER_ICON_NAME else: - self.assertEqual(icon_name, gui.SHARE_ICON_NAME) - - self.assertEqual(item.volume_id, volume['volume_id']) - self.assertEqual(item.volume_path, expected_path) + icon_name = gui.SHARE_ICON_NAME - # tooltips are correct - self.assertEqual(item.toolTip(gui.FOLDER_NAME_COL), name) - self.assertEqual(item.toolTip(gui.EXPLORE_COL), - gui.FOLDERS_COLUMN_EXPLORE) - - # explore button is in place - model_index = folders.indexFromItem(item, gui.EXPLORE_COL) - button = folders.indexWidget(model_index) - self.assertEqual(button.isFlat(), True) - self.assertEqual(button.isEnabled(), - bool(volume['subscribed'])) + self.assert_folder_row_correct(item, name, icon_name, volume, + tweaked_path=expected_path) treeiter += 1 item = treeiter.value() @@ -284,9 +334,12 @@ self.assertTrue(self.memento.check_warning(path, 'does not exist')) self.assertEqual(self._called, False) - def test_process_info_with_music_folder(self): + def test_process_info_with_music_folder(self, volumes=None): """The volumes info is processed when ready.""" - self.ui.process_info(FAKE_VOLUMES_MINIMAL_INFO) + if volumes is None: + volumes = FAKE_VOLUMES_MINIMAL_INFO + + self.ui.process_info(volumes) folders = self.ui.ui.folders treeiter = gui.QtGui.QTreeWidgetItemIterator(folders) @@ -298,25 +351,79 @@ treeiter += 1 item = treeiter.value() - volume = MUSIC_FOLDER + volume = volumes[0][2][1] + + self.assert_folder_row_correct(item, gui.MUSIC_DISPLAY_NAME, + gui.MUSIC_ICON_NAME, volume) - label = item.text(gui.FOLDER_NAME_COL) - self.assertEqual(label, gui.MUSIC_DISPLAY_NAME) + def test_focus_order(self): + """Ensure that the inner widgets are in the correct tab order.""" + self.ui.process_info(FAKE_VOLUMES_INFO) + folders = self.ui.ui.folders - subscribed = item.checkState(gui.SUBSCRIPTION_COL) == gui.CHECKED - self.assertEqual(subscribed, bool(volume['subscribed'])) + widget = self.ui.ui.folders.nextInFocusChain() + treeiter = gui.QtGui.QTreeWidgetItemIterator(folders) + for name, _, volumes in FAKE_VOLUMES_INFO: + item = treeiter.value() + sorted_vols = sorted(volumes, key=operator.itemgetter('path')) + for volume in sorted_vols: + treeiter += 1 + item = treeiter.value() # get child folder - icon_name = item.icon_obj.icon_name - self.assertEqual(icon_name, gui.MUSIC_ICON_NAME) + name = volume['path'].replace(USER_HOME + os.path.sep, '') + if volume['type'] == self.ui.backend.SHARE_TYPE: + name = volume['name'] + self.assertEqual(unicode(item.text(gui.FOLDER_NAME_COL)), name) - self.assertEqual(item.volume_id, volume['volume_id']) - self.assertEqual(item.volume_path, volume['path']) + if volume['type'] != self.ui.backend.ROOT_TYPE: + self.assertIsInstance(widget, QtGui.QCheckBox) + self.assertEqual(unicode( + self.ui.widget_items[widget].text(0)), name) + widget = widget.nextInFocusChain() + + if not self.ui.remote_folders: + self.assertIsInstance(widget, QtGui.QPushButton) + self.assertEqual(unicode( + self.ui.widget_items[widget].text(0)), name) + widget = widget.nextInFocusChain() + + treeiter += 1 + item = treeiter.value() + + def test_widget_dict(self): + """Ensure the widget_items dictionary is full.""" + self.ui.process_info(FAKE_VOLUMES_INFO) + it = QtGui.QTreeWidgetItemIterator(self.ui.ui.folders) + while it.value(): + item = it.value() + checkbox = self.ui.ui.folders.itemWidget(item, + gui.SUBSCRIPTION_COL) + button = self.ui.ui.folders.itemWidget(item, + gui.EXPLORE_COL) + if checkbox: + self.assertEqual(self.ui.widget_items[checkbox], + item) + if button: + self.assertEqual(self.ui.widget_items[button], + item) + it += 1 def test_share_publish_button(self): """When clicking the share/publish button, the proper url is opened.""" + self.assertTrue(self.ui.ui.share_publish_button.isVisible()) self.assert_uri_hook_called(self.ui.ui.share_publish_button, gui.MANAGE_FILES_LINK) + def test_add_folder_button(self): + """The 'add_folder_button' is visible by default.""" + self.assertEqual(self.ui.ui.add_folder_button.add_folder_func, + self.ui.backend.create_folder) + self.assertTrue(self.ui.ui.add_folder_button.isVisible()) + + def test_check_settings_button(self): + """The 'check_settings_button' is not visible by default.""" + self.assertFalse(self.ui.ui.check_settings_button.isVisible()) + class FoldersPanelAddFolderTestCase(FoldersPanelTestCase): """The test suite for the folder creation from a local dir.""" @@ -351,16 +458,25 @@ class FoldersPanelSubscriptionTestCase(FoldersPanelTestCase): """The test suite for the folder subscription.""" + faked_volumes = FAKE_VOLUMES_MINIMAL_INFO + @defer.inlineCallbacks def setUp(self): yield super(FoldersPanelSubscriptionTestCase, self).setUp() self.patch(gui.os.path, 'exists', lambda path: True) FakedDialog.response = gui.YES - self.ui.process_info(FAKE_VOLUMES_MINIMAL_INFO) + self.ui.process_info(self.faked_volumes) # the music folder self.item = self.ui.ui.folders.topLevelItem(0).child(1) + def set_item_checked(self, item=None, checked=True): + """Make item to be checked.""" + if item is None: + item = self.item + test = super(FoldersPanelSubscriptionTestCase, self).set_item_checked + test(item=item, checked=checked) + @defer.inlineCallbacks def test_on_folders_item_changed(self): """Clicking on 'subscribed' updates the folder subscription.""" @@ -368,10 +484,9 @@ volume = MUSIC_FOLDER fid = volume['volume_id'] subscribed = not bool(volume['subscribed']) - check_state = gui.CHECKED if subscribed else gui.UNCHECKED self.ui.is_processing = True - self.item.setCheckState(gui.SUBSCRIPTION_COL, check_state) + self.set_item_checked(self.item, subscribed) self.ui.is_processing = False yield self.ui.on_folders_itemChanged(self.item) @@ -380,7 +495,7 @@ self.assert_backend_called('change_volume_settings', fid, {'subscribed': subscribed}) - value = self.item.checkState(gui.SUBSCRIPTION_COL) == gui.CHECKED + value = self.get_item_checked(self.item) self.assertEqual(value, bool(subscribed)) # folder list was reloaded @@ -430,7 +545,7 @@ # make sure the item is subscribed self.ui.is_processing = True - self.item.setCheckState(gui.SUBSCRIPTION_COL, gui.CHECKED) + self.set_item_checked() self.ui.is_processing = False yield self.ui.on_folders_itemChanged(self.item) @@ -449,7 +564,7 @@ # make sure the item is unsubscribed self.ui.is_processing = True - self.item.setCheckState(gui.SUBSCRIPTION_COL, gui.UNCHECKED) + self.set_item_checked() self.ui.is_processing = False yield self.ui.on_folders_itemChanged(self.item) @@ -464,7 +579,7 @@ # make sure the item is subscribed self.ui.is_processing = True - self.item.setCheckState(gui.SUBSCRIPTION_COL, gui.CHECKED) + self.set_item_checked() self.ui.is_processing = False yield self.ui.on_folders_itemChanged(self.item) @@ -473,8 +588,7 @@ self.assertNotIn('change_volume_settings', self.ui.backend._called) self.assertFalse(self.ui.is_processing) - subscribed = self.item.checkState(gui.SUBSCRIPTION_COL) == gui.CHECKED - self.assertEqual(subscribed, False) + self.assertFalse(self.get_item_checked(self.item)) @defer.inlineCallbacks def test_subscribe_does_not_call_backend_if_answer_is_no(self): @@ -483,7 +597,7 @@ # make sure the item is subscribed self.ui.is_processing = True - self.item.setCheckState(gui.SUBSCRIPTION_COL, gui.CHECKED) + self.set_item_checked() self.ui.is_processing = False yield self.ui.on_folders_itemChanged(self.item) @@ -492,15 +606,14 @@ self.assertNotIn('change_volume_settings', self.ui.backend._called) self.assertFalse(self.ui.is_processing) - subscribed = self.item.checkState(gui.SUBSCRIPTION_COL) == gui.CHECKED - self.assertEqual(subscribed, False) + self.assertFalse(self.get_item_checked(self.item)) @defer.inlineCallbacks def test_no_confirmation_if_unsubscribing(self): """The confirmation dialog is not shown if unsubscribing.""" # make sure the item is unsubscribed self.ui.is_processing = True - self.item.setCheckState(gui.SUBSCRIPTION_COL, gui.UNCHECKED) + self.set_item_checked(checked=False) self.ui.is_processing = False # the confirm dialog was not called so far @@ -511,3 +624,235 @@ self.assertTrue(FakedDialog.args is None, 'dialog was not run') self.assertTrue(FakedDialog.kwargs is None, 'dialog was hid') + + +class RemoteFoldersPanelTestCase(FoldersPanelVolumesInfoTestCase): + """The test case for the RemoteFoldersPanel widget.""" + + class_ui = gui.RemoteFoldersPanel + + def test_process_info_with_music_folder(self, volumes=None): + """The volumes info is processed when ready.""" + volumes = volumes_with_music_unsubscribed() + parent = super(RemoteFoldersPanelTestCase, self) + parent.test_process_info_with_music_folder(volumes=volumes) + + def test_share_publish_button(self): + """When clicking the share/publish button, the proper url is opened.""" + self.assertFalse(self.ui.ui.share_publish_button.isVisible()) + + def test_add_folder_button(self): + """The 'add_folder_button' is not visible by default.""" + self.assertFalse(self.ui.ui.add_folder_button.isVisible()) + + def test_check_settings_button(self): + """The 'check_settings_button' is visible by default.""" + self.assertTrue(self.ui.ui.check_settings_button.isVisible()) + + +class RemoteFoldersPanelSubscriptionTestCase(FoldersPanelSubscriptionTestCase): + """The test suite for the remote folder subscription.""" + + class_ui = gui.RemoteFoldersPanel + faked_volumes = volumes_with_music_unsubscribed() + + +class BaseLocalFoldersTestCase(BaseTestCase): + """Test suite for the class implementing the LocalFolders feature.""" + + @defer.inlineCallbacks + def setUp(self): + self.path = 'not-existing-dir' + self.expected_size = self.build_test_dir(self.path) + self.queue = Queue.Queue() + yield super(BaseLocalFoldersTestCase, self).setUp() + + def build_test_dir(self, dir_path): + """Build a testing directory hierarchy.""" + assert not os.path.exists(dir_path) + + os.makedirs(dir_path) + self.addCleanup(shutil.rmtree, dir_path) + + total_size = 0 + + a_file = os.path.join(dir_path, 'test_file') + with open(a_file, 'wb') as f: + f.write('z' * 1000000) + + total_size += os.path.getsize(a_file) + + inner_dir = os.path.join(dir_path, 'test_dir') + os.mkdir(inner_dir) + + other_file = os.path.join(dir_path, 'other_test_file') + with open(other_file, 'wb') as f: + f.write(' ' * 99999) + + total_size += os.path.getsize(other_file) + + empty_dir = os.path.join(dir_path, 'empty') + os.mkdir(empty_dir) + + # add a symlink to confirm those are avoided + a_link = os.path.join(dir_path, 'some_link') + os.symlink(a_file, a_link) + + return total_size + + +class CalculateSizeTestCase(BaseLocalFoldersTestCase): + """Test suite for the CalculateSize thread implementation.""" + + @defer.inlineCallbacks + def setUp(self): + yield super(CalculateSizeTestCase, self).setUp() + self.ui = gui.CalculateSize(path_name=self.path, queue=self.queue) + self.patch(self.ui, 'start', lambda: None) + + def test_creation(self): + """The created instance is correct.""" + self.assertEqual(self.ui.path_name, self.path) + self.assertEqual(self.ui.queue, self.queue) + self.assertTrue(self.ui.daemon) + + def test_run(self): + """The run() method calculates the size for the given path.""" + self.ui.run() + + path, size = self.queue.get(block=True, timeout=0.5) + + self.assertEqual(path, self.path) + self.assertEqual(size, self.expected_size) + + def test_run_handles_errors(self): + """The run() method handles errors.""" + self.patch(gui.os, 'walk', helper_fail) + self.ui.run() + + self.assertRaises(Queue.Empty, self.queue.get, block=True, timeout=0.5) + + +class FakedCalculateSize(object): + """A faked CalculateSize thread.""" + + def __init__(self, *args, **kwargs): + self.started = False + + def start(self): + """Fake start.""" + self.started = True + + +class FolderItemTestCase(BaseLocalFoldersTestCase): + """Test suite for the FolderItem widget.""" + + @defer.inlineCallbacks + def setUp(self): + yield super(FolderItemTestCase, self).setUp() + self.calculator = FakedCalculateSize(self.path, self.queue) + self.patch(gui, 'CalculateSize', lambda *a, **kw: self.calculator) + self.values = ['foo', 'bar'] + + assert not self.calculator.started + + def test_no_params(self): + """The creation with no params uses defaults.""" + item = gui.FolderItem() + + self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '') + self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '') + self.assertEqual(item.path, None) + self.assertEqual(item.volume_id, None) + self.assertEqual(item.thread, None) + self.assertEqual(item.size, 0) + self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL), + gui.UNCHECKED) + + def test_values(self): + """The creation with only values.""" + item = gui.FolderItem(values=self.values) + + self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), self.values[0]) + self.assertEqual(item.text(gui.LOCAL_SPACE_COL), self.values[1]) + self.assertEqual(item.path, None) + self.assertEqual(item.volume_id, None) + self.assertEqual(item.thread, None) + self.assertEqual(item.size, 0) + self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL), + gui.UNCHECKED) + + def test_path(self): + """The creation with only a path.""" + item = gui.FolderItem(path=self.path) + + self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '') + self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '') + self.assertEqual(item.path, self.path) + self.assertEqual(item.volume_id, None) + self.assertEqual(item.thread, None) + self.assertEqual(item.size, 0) + self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL), + gui.UNCHECKED) + + def test_queue(self): + """The creation with only a queue.""" + item = gui.FolderItem(queue=self.queue) + + self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '') + self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '') + self.assertEqual(item.path, None) + self.assertEqual(item.volume_id, None) + self.assertEqual(item.thread, None) + self.assertEqual(item.size, 0) + self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL), + gui.UNCHECKED) + + def test_volume_id(self): + """The creation with only a volume_id.""" + item = gui.FolderItem(volume_id='yadda') + + self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '') + self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '') + self.assertEqual(item.path, None) + self.assertEqual(item.volume_id, 'yadda') + self.assertEqual(item.thread, None) + self.assertEqual(item.size, 0) + self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL), + gui.UNCHECKED) + + def test_path_and_volume_id(self): + """The creation with only a volume_id.""" + item = gui.FolderItem(path=self.path, volume_id='yadda') + + self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '') + self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '') + self.assertEqual(item.path, self.path) + self.assertEqual(item.volume_id, 'yadda') + self.assertEqual(item.thread, None) + self.assertEqual(item.size, 0) + self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL), + gui.CHECKED) + + def test_path_and_queue(self): + """The creation with only a volume_id.""" + item = gui.FolderItem(path=self.path, queue=self.queue) + + self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '') + self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '') + self.assertEqual(item.path, self.path) + self.assertEqual(item.volume_id, None) + self.assertEqual(item.thread, self.calculator) + self.assertEqual(item.size, None) + self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL), + gui.UNCHECKED) + + self.assertTrue(self.calculator.started) + + +class LocalFoldersPanelTestCase(UbuntuOneBinTestCase): + """Test suite for the LocalFoldersPanel widget.""" + + class_ui = gui.LocalFoldersPanel + + # TODO: add the test suite (LP: #959690). diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py 2012-03-21 14:06:28.000000000 +0000 @@ -18,8 +18,6 @@ """Tests for the GoToWebButton widget.""" -from twisted.internet import defer - from ubuntuone.controlpanel.gui import qt from ubuntuone.controlpanel.gui.qt import gotoweb as gui from ubuntuone.controlpanel.gui.qt.tests import ( @@ -32,9 +30,16 @@ class_ui = gui.GoToWebButton - @defer.inlineCallbacks - def setUp(self): - yield super(GoToWebButtonTestCase, self).setUp() + def test_uri_default(self): + """The uri uses the default.""" + self.assertEqual(self.ui.uri, self.class_ui.uri) + + def test_text_default(self): + """The text uses the default.""" + if self.class_ui.legend is not None: + self.assertEqual(self.ui.text(), self.class_ui.legend) + else: + self.assertEqual(self.ui.text(), '') def test_uri_can_be_set(self): """The uri can be set.""" @@ -42,10 +47,6 @@ self.ui.uri = uri self.assertEqual(self.ui.uri, uri) - def test_layout_direction(self): - """The layout direction is RightToLeft.""" - self.assertEqual(self.ui.layoutDirection(), gui.QtCore.Qt.RightToLeft) - def test_cursor_pointer(self): """The cursor is PointingHandCursor.""" self.assertEqual(self.ui.cursor().shape(), @@ -69,3 +70,17 @@ self.ui.click() self.assertEqual(self._called, False) + + +class GetStorageButtonTestCase(GoToWebButtonTestCase): + """The test suite for the GetStorageButton widget.""" + + class_ui = gui.GetStorageButton + + def test_uri(self): + """The default uri is correct.""" + self.assertEqual(self.ui.uri, gui.GET_STORAGE_LINK) + + def test_text(self): + """The default legend is correct.""" + self.assertEqual(self.ui.text(), gui.GET_MORE_STORAGE) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_gui.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_gui.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_gui.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_gui.py 2012-03-21 14:06:28.000000000 +0000 @@ -22,6 +22,17 @@ from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase +class FakeEntry(object): + + """A fake unity launcher entry.""" + + called = None + + def set_property(self, *args, **kwargs): + """Fake set_property.""" + self.called = (args, kwargs) + + class MainWindowTestCase(BaseTestCase): """Test the qt main window.""" @@ -58,3 +69,49 @@ self.assertFalse(self.ui.isVisible()) self.assertEqual(self._called, ((), {}), 'close_callback called.') + + def test_switch_to(self): + """Check that switch_to changes the current tab""" + self.assertEqual( + self.ui.ui.control_panel.ui.tab_widget.currentIndex(), + self.ui.ui.control_panel.ui.tab_widget.indexOf( + self.ui.ui.control_panel.ui.folders_tab)) + self.ui.switch_to("foobar") + self.assertEqual( + self.ui.ui.control_panel.ui.tab_widget.currentIndex(), + self.ui.ui.control_panel.ui.tab_widget.indexOf( + self.ui.ui.control_panel.ui.folders_tab)) + self.ui.switch_to("devices") + self.assertEqual( + self.ui.ui.control_panel.ui.tab_widget.currentIndex(), + self.ui.ui.control_panel.ui.tab_widget.indexOf( + self.ui.ui.control_panel.ui.devices_tab)) + self.ui.switch_to("settings") + self.assertEqual( + self.ui.ui.control_panel.ui.tab_widget.currentIndex(), + self.ui.ui.control_panel.ui.tab_widget.indexOf( + self.ui.ui.control_panel.ui.preferences_tab)) + self.ui.switch_to("account") + self.assertEqual( + self.ui.ui.control_panel.ui.tab_widget.currentIndex(), + self.ui.ui.control_panel.ui.tab_widget.indexOf( + self.ui.ui.control_panel.ui.account_tab)) + self.ui.switch_to("folders") + self.assertEqual( + self.ui.ui.control_panel.ui.tab_widget.currentIndex(), + self.ui.ui.control_panel.ui.tab_widget.indexOf( + self.ui.ui.control_panel.ui.folders_tab)) + + def test_focus_in(self): + """Test that focusing removes urgent bit from launcher entry.""" + entry = FakeEntry() + self.patch(self.ui, "entry", entry) + self.ui.focusInEvent(None) + self.assertEqual(entry.called, (('urgent', False), {})) + + def test_set_urgent(self): + """Test that set_urgent calls with the right arguments.""" + entry = FakeEntry() + self.patch(self.ui, "entry", entry) + self.ui.set_urgent("foo") + self.assertEqual(entry.called, (('urgent', "foo"), {})) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_signin.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_signin.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_signin.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_signin.py 2012-03-21 14:06:28.000000000 +0000 @@ -47,11 +47,12 @@ def test_label_is_correct(self): """The welcome_label has the correct text.""" - self.assertEqual(self.ui.ui.welcome_label.text(), gui.WELCOME_LABEL) + self.assertEqual(unicode(self.ui.ui.welcome_label.text()), + gui.WELCOME_MARKUP.format(gui.WELCOME_LABEL)) def test_buttos_are_correct(self): """The buttos have the correct text.""" - self.assertEqual(self.ui.ui.login_button.text(), + self.assertEqual(unicode(self.ui.ui.login_button.text()), gui.EXISTING_ACCOUNT_CHOICE_BUTTON) - self.assertEqual(self.ui.ui.register_button.text(), + self.assertEqual(unicode(self.ui.ui.register_button.text()), gui.SET_UP_ACCOUNT_CHOICE_BUTTON) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_wizard.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_wizard.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/tests/test_wizard.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/tests/test_wizard.py 2012-03-21 14:06:28.000000000 +0000 @@ -22,10 +22,145 @@ from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase, TOKEN +BUTTONS = [ + 'BackButton', 'CancelButton', 'CommitButton', + 'CustomButton1', 'CustomButton2', 'CustomButton3', + 'FinishButton', 'HelpButton', 'NextButton', +] + + +class AreYouSureTestCase(BaseTestCase): + """Test suite for the "Are you sure?" dialog.""" + + class_ui = gui.AreYouSure + + def test_title(self): + """Check the window title.""" + self.assertEqual(self.ui.windowTitle(), gui.APP_NAME) + self.assertEqual(self.ui.ui.title_label.text(), gui.ARE_YOU_SURE_TITLE) + + def test_message_label(self): + """Check the message label text.""" + link = gui.LINK_STYLE.format(link_url=gui.UBUNTUONE_LINK, + link_text=gui.UBUNTUONE_LINK) + msg = u'%s

%s' % (gui.ARE_YOU_SURE_SUBTITLE, + gui.ARE_YOU_SURE_HELP.format(support_url=link)) + self.assertEqual(unicode(self.ui.ui.message_label.text()), msg) + + def test_buttons(self): + """The buttons have the correct text.""" + self.assertEqual(self.ui.ui.yes_button.text(), gui.ARE_YOU_SURE_YES) + self.assertEqual(self.ui.ui.no_button.text(), gui.ARE_YOU_SURE_NO) + + +class UbuntuOnePageTestCase(BaseTestCase): + """Test the UbuntuOnePage widget.""" + + class_ui = gui.UbuntuOnePage + main_title = '' + panel_class = gui.QtGui.QFrame + sub_title = '' + + def test_panel_class(self): + """The panel_class is correct.""" + self.assertIsInstance(self.ui.panel, self.panel_class) + + def test_panel_is_created(self): + """The panel is properly added to the layout.""" + self.assertEqual(self.ui.layout().itemAt(2).widget(), self.ui.panel) + + def test_title(self): + """The title is correct.""" + self.assertIn(self.main_title, self.ui.title()) # avoid markup + + def test_subtitle(self): + """The subtitle is correct.""" + self.assertEqual(self.ui.subTitle(), self.sub_title) + + def test_error_label(self): + """The error label is hidden.""" + self.assertFalse(self.ui.form_errors_label.isVisible()) + + def test_is_final(self): + """The page is not final.""" + # from the doc: + + # After calling setFinalPage(true), isFinalPage() returns true and the + # Finish button is visible (and enabled if isComplete() returns true). + + # After calling setFinalPage(false), isFinalPage() returns true if + #nextId() returns -1; otherwise, it returns false. + + self.patch(self.ui, 'nextId', lambda *a: 0) + self.assertFalse(self.ui.isFinalPage()) + + +class LicensePageTestCase(UbuntuOnePageTestCase): + """Test the LicensePage wizard page.""" + + class_ui = gui.LicensePage + panel_class = gui.QtGui.QTextBrowser + + def test_content(self): + """The page content is correct.""" + expected = gui.QtGui.QTextBrowser() + expected.setHtml(gui.LICENSE_CONTENT) + self.assertEqual(self.ui.panel.toHtml(), expected.toHtml()) + + +class SignInPageTestCase(UbuntuOnePageTestCase): + """Test the SignInPage wizard page.""" + + class_ui = gui.SignInPage + panel_class = gui.SignInPanel + + +class CloudToComputerPageTestCase(UbuntuOnePageTestCase): + """Test the CloudToComputerPage wizard page.""" + + class_ui = gui.CloudToComputerPage + main_title = gui.CLOUD_TO_COMPUTER_TITLE + panel_class = gui.RemoteFoldersPanel + sub_title = gui.CLOUD_TO_COMPUTER_SUBTITLE + + def test_folder_panel_shows_remote_folders_only(self): + """The FolderPanel shows only remote folders.""" + self.assertTrue(self.ui.panel.remote_folders) + + +class SettingsPageTestCase(UbuntuOnePageTestCase): + """Test the SettingsPage wizard page.""" + + class_ui = gui.SettingsPage + panel_class = gui.PreferencesPanel + + +class ComputerToCloudPageTestCase(UbuntuOnePageTestCase): + """Test the ComputerToCloudPage wizard page.""" + + class_ui = gui.ComputerToCloudPage + main_title = gui.COMPUTER_TO_CLOUD_TITLE + panel_class = gui.LocalFoldersPanel + sub_title = gui.COMPUTER_TO_CLOUD_SUBTITLE + + def test_is_final(self): + """The page is not final.""" + self.assertTrue(self.ui.isFinalPage()) + + class UbuntuOneWizardTestCase(BaseTestCase): - """Test the UbuntuOneWizard.""" + """Test the UbuntuOneWizard widget.""" class_ui = gui.UbuntuOneWizard + confirm_response = gui.QtGui.QDialog.Accepted + show_license = False + + @defer.inlineCallbacks + def setUp(self): + self.patch(self.class_ui, 'show_license', self.show_license) + yield super(UbuntuOneWizardTestCase, self).setUp() + self.patch(self.ui.confirm_dialog, 'exec_', + lambda: self.confirm_response) def test_options(self): """Tne wizard options are correct.""" @@ -40,59 +175,58 @@ """Tne wizard style is Modern.""" self.assertEqual(self.ui.wizardStyle(), self.ui.ModernStyle) - def test_cancel_button(self): - """Send the rejected signal when the cancel button is clicked.""" - button = self.ui.button(self.ui.CancelButton) - self.assertEqual(button.text(), gui.CLOSE_AND_SETUP_LATER) + def test_side_widget(self): + """The side widget is correct.""" + self.assertIsInstance(self.ui.side_widget, gui.SideWidget) + self.assertIs(self.ui.sideWidget(), self.ui.side_widget) - self.ui.rejected.connect(self._set_called) - button.click() + def test_confirm_dialog(self): + """The confirm dialog is correct.""" + self.assertIsInstance(self.ui.confirm_dialog, gui.AreYouSure) - self.assertEqual(self._called, ((), {})) + def test_first_page(self): + """The first page is the correct one.""" + if self.show_license: + expected = self.ui.pages[self.ui.license_page] + else: + expected = self.ui.pages[self.ui.signin_page] - def test_button_layout(self): - """The button layout is correct.""" - buttons = [ - 'BackButton', 'CommitButton', 'CustomButton1', 'CustomButton2', - 'CustomButton3', 'FinishButton', 'HelpButton', 'NextButton', - ] - for button_name in buttons: - button = self.ui.button(getattr(self.ui, button_name)) - self.assertFalse(button.isVisible(), - 'Button %s should not be visible.' % button_name) + self.assertEqual(self.ui.startId(), expected) - button = self.ui.button(self.ui.CancelButton) - self.assertTrue(button.isVisible(), - 'Cancel button should not be visible.') + def test_done_accepted(self): + """When the wizard reached the end, emit finished.""" + self.ui.finished.connect(self._set_called) - def test_first_page(self): - """The first page is the correct one.""" - self.assertEqual(self.ui.startId(), - self.ui.pages[self.ui.signin_page]) + self.ui.done(gui.QtGui.QDialog.Accepted) - def test_tab_order(self): - """The button tab order is correct.""" - # can not be tested due to Qt API limitations + self.assertEqual(self._called, ((gui.QtGui.QDialog.Accepted,), {})) -class SideWidgetTestCase(UbuntuOneWizardTestCase): - """Test the side widget in the wizard.""" +class LicensedUbuntuOneWizardTestCase(UbuntuOneWizardTestCase): + """Test the LicensedUbuntuOneWizard.""" - def test_is_there(self): - """The side widget is correct.""" - self.assertIsInstance(self.ui.side_widget, gui.SideWidget) - self.assertIs(self.ui.sideWidget(), self.ui.side_widget) + show_license = True -class SignInPageTestCase(UbuntuOneWizardTestCase): +class UbuntuOneWizardSignInTestCase(UbuntuOneWizardTestCase): """Test the SignInPage wizard page.""" + buttons = {'CancelButton': (gui.CLOSE_AND_SETUP_LATER, 'rejected', ())} page_name = 'signin' + stage_name = page_name @defer.inlineCallbacks def setUp(self): - yield super(SignInPageTestCase, self).setUp() + yield super(UbuntuOneWizardSignInTestCase, self).setUp() self.page = getattr(self.ui, '%s_page' % self.page_name) + self._move_to_this_page() + + def _move_to_this_page(self): + """Fake the wizard is moved to this page.""" + page_id = self.ui.pages[self.page] + if self.ui.currentId() != page_id: + self.ui._next_id = page_id + self.ui.next() def test_was_added(self): """The SignInPage is added correctly.""" @@ -103,8 +237,119 @@ """The page is enabled at startup.""" self.assertTrue(self.page.isEnabled()) + def test_focus_order(self): + """The focus order is correct.""" + # can not be tested due to Qt API limitations + + def test_button_layout(self): + """The button layout is correct.""" + msg = '%s should not be visible.' + for button_name in BUTTONS: + button = self.ui.button(getattr(self.ui, button_name)) + if button_name in self.buttons: + self.assertTrue(button.isVisible(), + '%s should be visible.' % button_name) + else: + self.assertFalse(button.isVisible(), msg % button_name) + + def test_side_widget_stage(self): + """When in this page, the side_widget' stage is correct.""" + self.assertEqual(self.ui.side_widget.stage, + getattr(self.ui.side_widget, '%s_stage' % self.stage_name)) + + def test_buttons_behavior(self): + """Send the rejected signal when the cancel button is clicked.""" + msg = '%r should emit %r with args %r when clicked.' + + for name, (text, signal, signal_args) in self.buttons.iteritems(): + button = self.ui.button(getattr(self.ui, name)) + + if text is not None: + self.assertEqual(unicode(button.text()), text) + + getattr(self.ui, signal).connect(self._set_called) + button.click() + + self.assertEqual(self._called, (signal_args, {}), + msg % (name, signal, signal_args)) + self._called = False + + def test_done_rejected(self): + """When the wizard was cancelled and user confirmed, finish.""" + self.patch(self, 'confirm_response', gui.QtGui.QDialog.Accepted) + self.ui.rejected.connect(self._set_called) + + assert not self.ui.page(self.ui.currentId()).isFinalPage() + self.ui.done(gui.QtGui.QDialog.Rejected) + + self.assertEqual(self._called, ((), {})) + + def test_done_rejected_confirmation_rejected(self): + """When the wizard was cancelled but user unconfimed, do not finish.""" + self.patch(self, 'confirm_response', gui.QtGui.QDialog.Rejected) + self.ui.accepted.connect(self._set_called) + self.ui.rejected.connect(self._set_called) + self.ui.finished.connect(self._set_called) + + assert not self.ui.page(self.ui.currentId()).isFinalPage() + self.ui.done(gui.QtGui.QDialog.Rejected) + + self.assertEqual(self._called, False) + + +class UbuntuOneWizardCloudToComputerTestCase(UbuntuOneWizardSignInTestCase): + """Test the CloudToComputerPage wizard page.""" + + buttons = {'NextButton': (None, 'currentIdChanged', (3,))} + page_name = 'cloud_folders' + stage_name = 'folders' + + +class UbuntuOneWizardSettingsTestCase(UbuntuOneWizardSignInTestCase): + """Test the CloudToComputerPage wizard page.""" + + buttons = {'BackButton': (None, 'currentIdChanged', (0,))} + page_name = 'settings' + stage_name = 'folders' + -class LoginTestCase(UbuntuOneWizardTestCase): +class UbuntuOneWizardComputerToCloudTestCase(UbuntuOneWizardSignInTestCase): + """Test the CloudToComputerPage wizard page.""" + + buttons = { + 'FinishButton': (None, 'finished', (gui.QtGui.QDialog.Accepted,)), + 'BackButton': (None, 'currentIdChanged', (0,)), + } + page_name = 'local_folders' + stage_name = 'sync' + + def test_done_rejected(self): + """When the wizard is closed on the final page, emit rejected.""" + self.ui.finished.connect(self._set_called) + + assert self.ui.page(self.ui.currentId()).isFinalPage() + self.ui.done(gui.QtGui.QDialog.Rejected) + + self.assertEqual(self._called, ((gui.QtGui.QDialog.Rejected,), {})) + + def test_done_rejected_confirmation_rejected(self): + """When the wizard was cancelled but user unconfimed, do not finish.""" + # does not apply to this page + + +class UbuntuOneWizardLicensePage(UbuntuOneWizardSignInTestCase): + """Test the LicensePage wizard page.""" + + buttons = { + 'NextButton': (gui.LICENSE_AGREE, 'currentIdChanged', (1,)), + 'CancelButton': (gui.LICENSE_DISAGREE, 'rejected', ()), + } + page_name = 'license' + show_license = True + stage_name = 'install' + + +class UbuntuOneWizardLoginTestCase(UbuntuOneWizardTestCase): """Test the login through the wizard.""" method = 'login' @@ -112,7 +357,7 @@ @defer.inlineCallbacks def test_with_credentials(self): """Wizard is done when credentials were retrieved.""" - self.ui.finished.connect(self._set_called) + self.ui.currentIdChanged.connect(self._set_called) d = defer.succeed(TOKEN) def check(): @@ -128,12 +373,13 @@ yield d self.assertTrue(self.ui.signin_page.isEnabled()) - self.assertEqual(self._called, ((1,), {})) + expected_next_id = self.ui.pages[self.ui.cloud_folders_page] + self.assertEqual(self._called, ((expected_next_id,), {})) @defer.inlineCallbacks def test_without_credentials(self): """Wizard is done when credentials were retrieved.""" - self.ui.finished.connect(self._set_called) + self.ui.currentIdChanged.connect(self._set_called) d = defer.succeed(None) def check(): @@ -152,7 +398,7 @@ self.assertFalse(self._called) -class RegisterTestCase(LoginTestCase): +class UbuntuOneWizardRegisterTestCase(UbuntuOneWizardLoginTestCase): """Test the register through the wizard.""" method = 'register' diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/wizard.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/wizard.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/qt/wizard.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/qt/wizard.py 2012-03-21 14:06:28.000000000 +0000 @@ -19,26 +19,116 @@ from PyQt4 import QtGui, QtCore from twisted.internet import defer +from ubuntu_sso.qt import LINK_STYLE +from ubuntu_sso.qt.sso_wizard_page import BaseWizardPage from ubuntu_sso.utils.ui import CLOSE_AND_SETUP_LATER from ubuntuone.controlpanel import cache +from ubuntuone.controlpanel.logger import log_call, setup_logging +from ubuntuone.controlpanel.gui import ( + APP_NAME, + ARE_YOU_SURE_HELP, + ARE_YOU_SURE_NO, + ARE_YOU_SURE_SUBTITLE, + ARE_YOU_SURE_TITLE, + ARE_YOU_SURE_YES, + CLOUD_TO_COMPUTER_SUBTITLE, + CLOUD_TO_COMPUTER_TITLE, + COMPUTER_TO_CLOUD_SUBTITLE, + COMPUTER_TO_CLOUD_TITLE, + LICENSE_AGREE, + LICENSE_AGREEMENT, + LICENSE_BASIC, + LICENSE_DISAGREE, + LICENSE_GPL3, + LICENSE_LINK, + UBUNTUONE_LINK, +) +from ubuntuone.controlpanel.gui.qt.folders import ( + RemoteFoldersPanel, + LocalFoldersPanel, +) +from ubuntuone.controlpanel.gui.qt.preferences import PreferencesPanel from ubuntuone.controlpanel.gui.qt.signin import SignInPanel from ubuntuone.controlpanel.gui.qt.side_widget import SideWidget +from ubuntuone.controlpanel.gui.qt.ui import are_you_sure_ui -class UbuntuOnePage(QtGui.QWizardPage): +logger = setup_logging('qt.wizard') + + +GPL_URL = u'http://www.gnu.org/licenses' +GPL_LINK = LINK_STYLE.format(link_url=GPL_URL, link_text=GPL_URL) +LICENSE_CONTENT = u""" + +

{license_agreement}

+

{license_gpl3}

+

{license_basic}

+

{license_link}

+ +""".format(license_agreement=LICENSE_AGREEMENT, + license_gpl3=LICENSE_GPL3, license_basic=LICENSE_BASIC, + license_link=LICENSE_LINK.format(license_link=GPL_LINK), +) + + +class AreYouSure(QtGui.QDialog): + + """A 'Are you sure?' dialog.""" + + def __init__(self, *args, **kwargs): + super(AreYouSure, self).__init__(*args, **kwargs) + self.ui = are_you_sure_ui.Ui_Dialog() + self.ui.setupUi(self) + self.setWindowTitle(APP_NAME) + + self.ui.title_label.setText(ARE_YOU_SURE_TITLE) + + link = LINK_STYLE.format(link_url=UBUNTUONE_LINK, + link_text=UBUNTUONE_LINK) + msg = u'%s

%s' % (ARE_YOU_SURE_SUBTITLE, + ARE_YOU_SURE_HELP.format(support_url=link)) + self.ui.message_label.setText(msg) + + self.ui.yes_button.setText(ARE_YOU_SURE_YES) + self.ui.no_button.setText(ARE_YOU_SURE_NO) + + +class UbuntuOnePage(BaseWizardPage): """A generic page for the UbuntuOneWizard.""" - panel_class = None + is_final = False + main_title = None + max_width = 5000 + panel_class = QtGui.QFrame + sub_title = None def __init__(self, *args, **kwargs): super(UbuntuOnePage, self).__init__(*args, **kwargs) - self.layout = QtGui.QVBoxLayout(self) self.panel = None if self.panel_class is not None: - self.panel = SignInPanel() - self.layout.addWidget(self.panel) + self.panel = self.panel_class() + self.layout().addWidget(self.panel) + + if self.main_title is not None: + self.setTitle(self.main_title) + + if self.sub_title is not None: + self.setSubTitle(self.sub_title) + + self.form_errors_label.hide() + self.setFinalPage(self.is_final) + + +class LicensePage(UbuntuOnePage): + """The page to show the license.""" + + panel_class = QtGui.QTextBrowser + + def __init__(self, *args, **kwargs): + super(LicensePage, self).__init__(*args, **kwargs) + self.panel.setHtml(LICENSE_CONTENT) class SignInPage(UbuntuOnePage): @@ -47,9 +137,38 @@ panel_class = SignInPanel +class CloudToComputerPage(UbuntuOnePage): + """The page to choose cloud folders to sync locally.""" + + main_title = CLOUD_TO_COMPUTER_TITLE + panel_class = RemoteFoldersPanel + sub_title = CLOUD_TO_COMPUTER_SUBTITLE + + def __init__(self, *args, **kwargs): + super(CloudToComputerPage, self).__init__(*args, **kwargs) + self.panel.ui.add_folder_button.hide() + + +class SettingsPage(UbuntuOnePage): + """The page to adjust the service settings.""" + + panel_class = PreferencesPanel + + +class ComputerToCloudPage(UbuntuOnePage): + """The page to choose local folders to sync remotly.""" + + is_final = True + main_title = COMPUTER_TO_CLOUD_TITLE + panel_class = LocalFoldersPanel + sub_title = COMPUTER_TO_CLOUD_SUBTITLE + + class UbuntuOneWizard(cache.Cache, QtGui.QWizard): """The Ubuntu One wizard.""" + show_license = False # do not change unless you know what you're doing + def __init__(self, *args, **kwargs): super(UbuntuOneWizard, self).__init__(*args, **kwargs) self.pages = {} @@ -58,13 +177,19 @@ self.setOption(self.HaveFinishButtonOnEarlyPages, False) self.setWizardStyle(self.ModernStyle) - self.setButtonText(self.CancelButton, CLOSE_AND_SETUP_LATER) - self.setButtonLayout([self.Stretch, self.CancelButton]) + self.confirm_dialog = AreYouSure(self) self.side_widget = SideWidget() self.side_widget.stage = self.side_widget.signin_stage self.setSideWidget(self.side_widget) + # license + self.license_page = LicensePage() + self.next_button_text = self.button(self.NextButton).text() + if self.show_license: + self.addPage(self.license_page) + + # sign in self.signin_page = SignInPage() self.addPage(self.signin_page) @@ -72,10 +197,23 @@ self.signin_page.panel.ui.register_button.clicked.connect( self.register) - self.setTabOrder(self.signin_page.panel.ui.login_button, - self.signin_page.panel.ui.register_button) - self.setTabOrder(self.signin_page.panel.ui.register_button, - self.button(self.CancelButton)) + # cloud to compuer + self.cloud_folders_page = CloudToComputerPage() + self.addPage(self.cloud_folders_page) + + self.cloud_folders_page.panel.ui.check_settings_button.clicked.connect( + self.check_settings) + + # settings + self.settings_page = SettingsPage() + self.addPage(self.settings_page) + + # computer to cloud + self.local_folders_page = ComputerToCloudPage() + self.addPage(self.local_folders_page) + + self._next_id = self.pages[self.signin_page] + self.next() # pylint: disable=C0103 @@ -84,8 +222,86 @@ page_id = super(UbuntuOneWizard, self).addPage(page) self.pages[page] = page_id + @log_call(logger.info) + def initializePage(self, page_id): + """The wizard will show the page 'page_id'.""" + page = self.page(page_id) + logger.debug('UbuntuOneWizard.initializePage: page is %r.', page) + + button_layout = button_to = button = stage = None + + if page is self.license_page: + button_layout = [self.Stretch, self.CancelButton, self.NextButton] + button = self.button(self.NextButton) + button_to = self.button(self.CancelButton) + stage = self.side_widget.install_stage + + self.setButtonText(self.NextButton, LICENSE_AGREE) + self.setButtonText(self.CancelButton, LICENSE_DISAGREE) + + elif page is self.signin_page: + button_layout = [self.Stretch, self.CancelButton] + button = self.signin_page.panel.ui.register_button + button_to = self.button(self.CancelButton) + stage = self.side_widget.signin_stage + self._next_id = self.pages[self.cloud_folders_page] + + self.setButtonText(self.CancelButton, CLOSE_AND_SETUP_LATER) + self.setTabOrder(self.signin_page.panel.ui.login_button, button) + + elif page is self.cloud_folders_page: + self.setButtonText(self.NextButton, self.next_button_text) + + button_layout = [self.Stretch, self.NextButton] + button = self.cloud_folders_page.panel.ui.check_settings_button + button_to = self.button(self.NextButton) + stage = self.side_widget.folders_stage + self._next_id = self.pages[self.local_folders_page] + elif page is self.settings_page: + button_layout = [self.Stretch, self.BackButton] + button = self.settings_page.panel.ui.apply_changes_button + button_to = self.button(self.BackButton) + stage = self.side_widget.folders_stage + self._next_id = self.pages[self.cloud_folders_page] + elif page is self.local_folders_page: + button_layout = [self.Stretch, self.BackButton, self.FinishButton] + button = self.local_folders_page.panel.ui.add_folder_button + button_to = self.button(self.BackButton) + stage = self.side_widget.sync_stage + else: + logger.error('UbuntuOneWizard.initializePage was called with an' + 'unknown page: %r (page_id was %r).', page, page_id) + + logger.info('UbuntuOneWizard.initializePage: new page is %r, ' + 'new button layout is %r, ' + 'new side widget stage is %r.', page, button_layout, stage) + + if button is not None and button_to is not None: + self.setTabOrder(button, button_to) + if button_layout is not None: + self.setButtonLayout(button_layout) + if stage is not None: + self.side_widget.stage = stage + + @log_call(logger.info) + def cleanupPage(self, page_id): + """Called clean up 'page_id' just before the user leaves it.""" + page = self.page(page_id) + logger.debug('UbuntuOneWizard.cleanupPage: page is %r.', page) + if page is self.settings_page or page is self.local_folders_page: + self.initializePage(self.pages[self.cloud_folders_page]) + + def nextId(self): + """Return the nextId to show.""" + return self._next_id + # pylint: enable=C0103 + def _process_credentials(self, credentials=None): + """Confirm which is the next step after analyzing 'credentials'.""" + if credentials: + self.next() + @QtCore.pyqtSlot() @defer.inlineCallbacks def login(self): @@ -104,7 +320,32 @@ self._process_credentials(credentials) self.setEnabled(True) - def _process_credentials(self, credentials=None): - """Confirm which is the next step after analyzing 'credentials'.""" - if credentials: - self.accept() + @QtCore.pyqtSlot() + def check_settings(self): + """Show the check settings page.""" + self._next_id = self.pages[self.settings_page] + self.next() + + def done(self, result): + """The main window is being closed, call any custom callback.""" + if result == QtGui.QDialog.Accepted: + parent_done = super(UbuntuOneWizard, self).done + f = lambda: parent_done(QtGui.QDialog.Accepted) + self.local_folders_page.panel.changesApplied.connect(f) + # commit local_folders_page's changes + self.local_folders_page.panel.apply_changes() + elif not self.page(self.currentId()).isFinalPage(): + response = self.confirm_dialog.exec_() + if response == QtGui.QDialog.Accepted: + logger.warning('UbuntuOneWizard: user canceled setup.') + self.rejected.emit() + elif (self.show_license and + self.currentId() == self.pages[self.license_page]): + response = self.confirm_dialog.exec_() + if response == QtGui.QDialog.Accepted: + logger.warning('UbuntuOneWizard: user wants to uninstall.') + # TODO: needs implementation in this project + ##qt.utils.uninstall_application() + self.rejected.emit() + else: + super(UbuntuOneWizard, self).done(result) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/tests/__init__.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/tests/__init__.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/gui/tests/__init__.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/gui/tests/__init__.py 2012-03-21 14:06:28.000000000 +0000 @@ -134,6 +134,7 @@ next_result = None exposed_methods = [] exposed_results = {} + raise_attr_error = True def __init__(self, *args, **kwargs): self._args = args @@ -142,6 +143,23 @@ for i in self.exposed_methods: setattr(self, i, self._record_call(i)) + def __call__(self, *args, **kwargs): + """Skip.""" + + def __getattribute__(self, attr_name): + super_getattr = super(FakedObject, self).__getattribute__ + + try: + result = super_getattr(attr_name) + except AttributeError: + if super_getattr('raise_attr_error'): + raise + else: + result = FakedObject() + result.raise_attr_error = super_getattr('raise_attr_error') + + return result + def _record_call(self, func_name): """Store values when calling 'func_name'.""" diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/logger.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/logger.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/logger.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/logger.py 2012-03-21 14:06:28.000000000 +0000 @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- - -# Authors: Natalia B Bidart # -# Copyright 2010 Canonical Ltd. +# Copyright 2010-2012 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published @@ -30,7 +28,7 @@ from ubuntuone.platform.xdg_base_directory import ubuntuone_log_dir -if os.environ.get('DEBUG'): +if os.environ.get('U1_DEBUG'): LOG_LEVEL = logging.DEBUG else: # Only log this level and above diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/tests/__init__.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/tests/__init__.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/tests/__init__.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/tests/__init__.py 2012-03-21 14:06:28.000000000 +0000 @@ -334,6 +334,15 @@ ] +class CustomError(Exception): + """Custom error for tests.""" + + +def helper_fail(*a, **kw): + """Helper to raise an exception, usually used when monkey-patching.""" + raise CustomError((a, kw)) + + class TestCase(BaseTestCase): """Basics for testing.""" diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/tests/test_login_client.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/tests/test_login_client.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/tests/test_login_client.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/tests/test_login_client.py 2012-03-21 14:06:28.000000000 +0000 @@ -21,11 +21,7 @@ from twisted.internet import defer from ubuntuone.controlpanel import login_client -from ubuntuone.controlpanel.tests import TestCase, TOKEN - - -class CustomError(Exception): - """Custom error for tests.""" +from ubuntuone.controlpanel.tests import CustomError, TestCase, TOKEN class FakedCredentialsManagementTool(object): diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/tests/test_sd_client.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/tests/test_sd_client.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/tests/test_sd_client.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/tests/test_sd_client.py 2012-03-21 14:06:28.000000000 +0000 @@ -32,7 +32,7 @@ from ubuntuone.syncdaemon.interaction_interfaces import bool_str from ubuntuone.controlpanel import sd_client -from ubuntuone.controlpanel.tests import TestCase +from ubuntuone.controlpanel.tests import CustomError, TestCase # Instance of 'SyncDaemonTool' has no 'foo' member # pylint: disable=E1101 @@ -41,10 +41,6 @@ SAMPLE_LIMITS = {'upload': 999, 'download': 838} -class CustomError(Exception): - """Custom error for tests.""" - - class FakedSyncDaemonTool(object): """Fake the SyncDaemonTool.""" diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/tests/test_web_client.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/tests/test_web_client.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/tests/test_web_client.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/tests/test_web_client.py 2012-03-21 14:06:28.000000000 +0000 @@ -29,6 +29,7 @@ WebClient, WebClientError, ) +from ubuntuone.platform.credentials import APP_NAME SAMPLE_KEY = "result" @@ -104,6 +105,10 @@ self.wc = WebClient(sample_get_credentials, base_url=self.base_iri) self.addCleanup(self.wc.shutdown) + def test_correct_app_name(self): + """Assert that the wc uses the correct appname.""" + self.assertEqual(APP_NAME, self.wc.wc.appname) + @defer.inlineCallbacks def test_get_url(self): """A method is successfully called in the mock webservice.""" diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/utils/__init__.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/utils/__init__.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/utils/__init__.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/utils/__init__.py 2012-03-21 14:06:28.000000000 +0000 @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- - # -# Copyright 2010 Canonical Ltd. +# Copyright 2010-2012 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published @@ -37,10 +36,14 @@ if sys.platform == 'win32': from ubuntuone.controlpanel.utils import windows are_updates_present = windows.are_updates_present + default_folders = windows.default_folders perform_update = windows.perform_update else: + from ubuntuone.controlpanel.utils import linux are_updates_present = lambda *args, **kwargs: False + default_folders = linux.default_folders perform_update = lambda *args, **kwargs: None + # pylint: enable=C0103 diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/utils/linux.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/utils/linux.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/utils/linux.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/utils/linux.py 2012-03-21 14:06:28.000000000 +0000 @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2012 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranties of +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . + +"""Miscelaneous functions and constants for linux.""" + +import codecs +import os + +from dirspec.basedir import xdg_config_home +from ubuntuone.controlpanel.logger import setup_logging + + +logger = setup_logging('utils.linux') + + +def default_folders(user_home='', dirs_path=None): + """Return a list of the folders to add by default.""" + result = [] + + if dirs_path is None: + dirs_path = os.path.join(xdg_config_home, u'user-dirs.dirs') + + if not os.path.exists(dirs_path): + logger.warning('default_folders: dirs_path %r does not exist.', + dirs_path) + return result + + # pylint: disable=W0702 + + try: + with codecs.open(dirs_path, encoding='utf-8') as f: + for line in f: + if line.startswith(u'#'): + continue + + try: + _, value = line.strip().split(u'=') + value = value.strip(u'"').replace(u'$HOME', user_home) + except: + logger.exception('default_folders: can not row %r:', line) + else: + result.append(value) + except: + logger.exception('default_folders: can not load file %r:', dirs_path) + + return result diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/utils/tests/test_linux.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/utils/tests/test_linux.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/utils/tests/test_linux.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/utils/tests/test_linux.py 2012-03-21 14:06:28.000000000 +0000 @@ -17,11 +17,35 @@ """Test the linux utils functions.""" +import codecs +import os from twisted.internet import defer from ubuntuone.controlpanel import utils -from ubuntuone.controlpanel.tests import TestCase +from ubuntuone.controlpanel.tests import TestCase, USER_HOME + +REAL_CONTENT = u""" +XDG_DESKTOP_DIR="$HOME/Desktop" +XDG_DOWNLOAD_DIR="$HOME/Downloads" +XDG_PUBLICSHARE_DIR="$HOME/Public" +XDG_DOCUMENTS_DIR="$HOME/Documents" +XDG_MUSIC_DIR="$HOME/Music" +XDG_PICTURES_DIR="$HOME/Pictures" +XDG_VIDEOS_DIR="$HOME/Videos" +""" + +NON_ASCII = u""" +XDG_DESKTOP_DIR="$HOME/桌面" +XDG_DOWNLOAD_DIR="$HOME/下载" +XDG_DOCUMENTS_DIR="$HOME/文档" +""" + +LATIN_1 = u""" +XDG_DESKTOP_DIR="$HOME/Súbeme" +XDG_DOWNLOAD_DIR="$HOME/Bájame" +XDG_DOCUMENTS_DIR="$HOME/Mis ñoños Documentos" +""" class AutoupdaterTestCase(TestCase): @@ -36,3 +60,79 @@ def test_perform_update(self): """Test the method that performs the update.""" self.assertEqual(None, utils.perform_update()) + + +class DefaultFoldersTestCase(TestCase): + """Test suite for the default_folders retriever.""" + + def _create_faked_file(self, content='', encoding=None): + """Create a faked file to be parsed and clean it up afterwards.""" + filename = './dirs-parser-test.dirs' + if os.path.exists(filename): + os.remove(filename) + + if encoding is None: + encoding = 'utf-8' + + with codecs.open(filename, 'w', encoding=encoding) as f: + f.write(content) + + self.addCleanup(os.remove, filename) + + return filename + + def test_default_folders_not_file(self): + """Default folders are empty if the file does not exist.""" + path = 'foo/bar/baz/yadda/yo' + assert not os.path.exists(path) + actual = utils.default_folders(dirs_path=path) + + self.assertEqual(actual, []) + + def test_default_folders_empty_file(self): + """Default folders are empty if the file empty.""" + filename = self._create_faked_file(content='') + actual = utils.default_folders(dirs_path=filename) + + self.assertEqual(actual, []) + + def test_default_folders_only_comments(self): + """Default folders are empty if only comments in the file.""" + content = u"# yadda yadda\n# foo bar baz\n#puipuipui" + filename = self._create_faked_file(content=content) + actual = utils.default_folders(dirs_path=filename) + + self.assertEqual(actual, []) + + def test_default_folders_syntax_error(self): + """Default folders do not explode on syntax error.""" + content = u"foo ~ bar\nYADDA=DOO" + filename = self._create_faked_file(content=content) + actual = utils.default_folders(dirs_path=filename) + + self.assertEqual(actual, [u'DOO']) + + def test_default_folders_parsed(self): + """Default folders parses the content of the file.""" + filename = self._create_faked_file(content=REAL_CONTENT) + actual = utils.default_folders(user_home=USER_HOME, dirs_path=filename) + + expected = (i.split(u'=')[1].strip(u'"') for i in REAL_CONTENT.split()) + expected = [i.replace(u'$HOME', USER_HOME) for i in expected] + self.assertEqual(actual, expected) + + def test_default_folders_non_ascii(self): + """Default folders parses the content if it's non-ascii.""" + filename = self._create_faked_file(content=NON_ASCII) + actual = utils.default_folders(user_home=USER_HOME, dirs_path=filename) + + expected = (i.split(u'=')[1].strip(u'"') for i in NON_ASCII.split()) + expected = [i.replace(u'$HOME', USER_HOME) for i in expected] + self.assertEqual(actual, expected) + + def test_default_folders_bad_encoding(self): + """Default folders parses the content if it's non-ascii.""" + filename = self._create_faked_file(content=LATIN_1, encoding='latin-1') + actual = utils.default_folders(user_home=USER_HOME, dirs_path=filename) + + self.assertEqual(actual, []) diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/utils/tests/test_windows.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/utils/tests/test_windows.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/utils/tests/test_windows.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/utils/tests/test_windows.py 2012-03-21 14:06:28.000000000 +0000 @@ -87,6 +87,47 @@ self.assertEqual(0, self._called[0][5]) +class DefaultFoldersTestCase(TestCase): + """Test the default_folders method.""" + + def test_special_folders(self, names=None): + """Test that default_folders does the right thing.""" + folders = utils.default_folders() + + if names is None: + names = ('PERSONAL', 'MYMUSIC', 'MYPICTURES', 'MYVIDEO') + + self.assertEqual(len(folders), len(names)) + self.assertEqual(folders, [os.path.abspath(f) for f in folders]) + + expected = [] + for name in names: + name = getattr(utils.windows.shellcon, 'CSIDL_%s' % name) + folder = utils.windows.shell.SHGetFolderPath(0, name, None, 0) + expected.append(folder.encode('utf8')) + + self.assertEqual(sorted(folders), sorted(expected)) + + def test_special_folders_with_error(self): + """Test that default_folders handles errors.""" + failing_name = 'MYVIDEO' + original_func = utils.windows.shell.SHGetFolderPath + + class SomeError(Exception): + """For test only.""" + + def fail_for_some(code, name, parent, flag): + """Custom failer.""" + fail = getattr(utils.windows.shellcon, 'CSIDL_%s' % failing_name) + if name == fail: + raise SomeError('The parameter is incorrect.') + else: + return original_func(code, name, parent, flag) + + self.patch(utils.windows.shell, 'SHGetFolderPath', fail_for_some) + self.test_special_folders(names=('PERSONAL', 'MYMUSIC', 'MYPICTURES')) + + class GetPathTestCase(TestCase): """Test the code that is used for the auto update process.""" diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/utils/windows.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/utils/windows.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/utils/windows.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/utils/windows.py 2012-03-21 14:06:28.000000000 +0000 @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License along # with this program. If not, see . -"""Autoupdate functions for windows.""" +"""Miscelaneous functions and constants for windows.""" import os import sys @@ -23,14 +23,15 @@ # Avoid pylint error on Linux # pylint: disable=F0401 import win32api -# pylint: enable=F0401 +from win32com.shell import shell, shellcon +# pylint: enable=F0401 from twisted.internet import defer from twisted.internet.utils import getProcessValue from ubuntuone.controlpanel.logger import setup_logging -logger = setup_logging('utils') +logger = setup_logging('utils.windows') AUTOUPDATE_EXE_NAME = 'autoupdate-windows.exe' @@ -63,6 +64,30 @@ defer.returnValue(result) +def default_folders(user_home=None): + """Return a list of the folders to add by default.""" + # as per http://msdn.microsoft.com/en-us/library/windows/desktop/bb762181, + # SHGetFolderPath is deprecated, we should migrate to SHGetKnownFolderPath + # (http://msdn.microsoft.com/en-us/library/windows/desktop/bb762188) + # but the latter does not support XP + # (Minimum supported client: Windows Vista) + get_path = lambda name: shell.SHGetFolderPath( + 0, getattr(shellcon, name), None, 0).encode('utf8') + + folders = [] + # More information on these constants at + # http://msdn.microsoft.com/en-us/library/bb762494 + for name in (u'PERSONAL', u'MYMUSIC', u'MYPICTURES', u'MYVIDEO'): + name = u'CSIDL_%s' % name + try: + folders.append(get_path(name)) + except: # pylint: disable=W0702 + logger.exception('default_folders: could not retrieve folder info ' + 'for name %r.', name) + + return folders + + def perform_update(): """Spawn the autoupdate process and call the stop function.""" update_path = _get_update_path() diff -Nru ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/web_client.py ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/web_client.py --- ubuntuone-control-panel-2.99.90/ubuntuone/controlpanel/web_client.py 2012-03-06 18:18:50.000000000 +0000 +++ ubuntuone-control-panel-2.99.91/ubuntuone/controlpanel/web_client.py 2012-03-21 14:06:28.000000000 +0000 @@ -30,7 +30,7 @@ from ubuntuone.controlpanel import WEBSERVICE_BASE_URL from ubuntuone.controlpanel.logger import setup_logging - +from ubuntuone.platform.credentials import APP_NAME logger = setup_logging('webclient') @@ -42,7 +42,7 @@ """Initialize the webclient.""" self.base_url = base_url self.get_credentials = get_credentials - self.wc = webclient_factory() + self.wc = webclient_factory(APP_NAME) logger.debug("WebClient created: base_url is %r, inner client is %r.", self.base_url, self.wc)