=== modified file 'bin/ubuntu-sso-login-qt'
--- bin/ubuntu-sso-login-qt	2012-02-13 15:43:59 +0000
+++ bin/ubuntu-sso-login-qt	2012-03-20 16:10:09 +0000
@@ -15,16 +15,19 @@
 # You should have received a copy of the GNU General Public License along
 # with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-"""Start the sso GTK UI."""
+"""Start the sso Qt UI."""
 
 # Invalid name "ubuntu-sso-login-qt", pylint: disable=C0103
 # Access to a protected member, pylint: disable=W0212
 
+import sys
+
 from ubuntu_sso.qt.main import main
 from ubuntu_sso.utils.ui import parse_args
 
-from dbus.mainloop.qt import DBusQtMainLoop
-DBusQtMainLoop(set_as_default=True)
+if sys.platform.startswith('linux'):
+    from dbus.mainloop.qt import DBusQtMainLoop
+    DBusQtMainLoop(set_as_default=True)
 
 
 if __name__ == "__main__":

=== modified file 'bin/ubuntu-sso-proxy-creds-qt'
--- bin/ubuntu-sso-proxy-creds-qt	2012-02-24 13:07:06 +0000
+++ bin/ubuntu-sso-proxy-creds-qt	2012-03-20 16:10:09 +0000
@@ -19,9 +19,12 @@
 
 # Invalid name, pylint: disable=C0103
 
-# set the dbus main loop to be used
-from dbus.mainloop.qt import DBusQtMainLoop
-DBusQtMainLoop(set_as_default=True)
+import sys
+
+if sys.platform.startswith('linux'):
+    # set the dbus main loop to be used
+    from dbus.mainloop.qt import DBusQtMainLoop
+    DBusQtMainLoop(set_as_default=True)
 
 from ubuntu_sso.qt.proxy_dialog import main
 

=== added file 'data/qt/linux.qss'
--- data/qt/linux.qss	1970-01-01 00:00:00 +0000
+++ data/qt/linux.qss	2012-03-20 16:10:09 +0000
@@ -0,0 +1,2 @@
+/* Styles specific to the linux platform */
+

=== modified file 'data/qt/loadingoverlay.ui'
--- data/qt/loadingoverlay.ui	2012-02-22 16:58:08 +0000
+++ data/qt/loadingoverlay.ui	2012-03-20 16:10:09 +0000
@@ -52,11 +52,6 @@
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
-          <property name="font">
-           <font>
-            <pointsize>14</pointsize>
-           </font>
-          </property>
           <property name="text">
            <string notr="true">Getting information, please wait...</string>
           </property>

=== modified file 'data/qt/proxy_credentials_dialog.ui'
--- data/qt/proxy_credentials_dialog.ui	2012-02-23 11:47:00 +0000
+++ data/qt/proxy_credentials_dialog.ui	2012-03-20 16:10:09 +0000
@@ -113,8 +113,6 @@
              </property>
              <property name="font">
               <font>
-               <pointsize>14</pointsize>
-               <weight>75</weight>
                <bold>true</bold>
               </font>
              </property>

=== modified file 'data/qt/reset_password.ui'
--- data/qt/reset_password.ui	2012-03-05 20:30:57 +0000
+++ data/qt/reset_password.ui	2012-03-20 16:10:09 +0000
@@ -6,14 +6,14 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>544</width>
-    <height>280</height>
+    <width>505</width>
+    <height>260</height>
    </rect>
   </property>
-  <property name="layoutDirection">
-   <enum>Qt::LeftToRight</enum>
+  <property name="windowTitle">
+   <string/>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout_6">
+  <layout class="QVBoxLayout" name="verticalLayout_4">
    <property name="spacing">
     <number>15</number>
    </property>
@@ -21,261 +21,215 @@
     <number>0</number>
    </property>
    <item>
-    <layout class="QHBoxLayout" name="horizontalLayout">
-     <property name="spacing">
-      <number>0</number>
-     </property>
-     <item>
-      <layout class="QVBoxLayout" name="verticalLayout_5">
-       <property name="spacing">
-        <number>15</number>
-       </property>
-       <item>
-        <layout class="QVBoxLayout" name="verticalLayout_4">
-         <property name="spacing">
-          <number>3</number>
-         </property>
-         <item>
-          <widget class="QLabel" name="reset_code">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="minimumSize">
-            <size>
-             <width>310</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>16777215</width>
-             <height>16777215</height>
-            </size>
-           </property>
-           <property name="font">
-            <font>
-             <weight>75</weight>
-             <bold>true</bold>
-            </font>
-           </property>
-           <property name="text">
-            <string notr="true">reset_code</string>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QLineEdit" name="reset_code_line_edit">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="minimumSize">
-            <size>
-             <width>300</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>300</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-       <item>
-        <layout class="QVBoxLayout" name="verticalLayout">
-         <property name="spacing">
-          <number>3</number>
-         </property>
-         <item>
-          <widget class="QLabel" name="password_label">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="minimumSize">
-            <size>
-             <width>310</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>16777215</width>
-             <height>16777215</height>
-            </size>
-           </property>
-           <property name="font">
-            <font>
-             <weight>75</weight>
-             <bold>true</bold>
-            </font>
-           </property>
-           <property name="text">
-            <string notr="true">password_label</string>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QLineEdit" name="password_line_edit">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="minimumSize">
-            <size>
-             <width>300</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>300</width>
-             <height>16777215</height>
-            </size>
-           </property>
-           <property name="echoMode">
-            <enum>QLineEdit::Password</enum>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-       <item>
-        <layout class="QVBoxLayout" name="verticalLayout_2">
-         <property name="spacing">
-          <number>3</number>
-         </property>
-         <item>
-          <widget class="QLabel" name="confirm_password_label">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="minimumSize">
-            <size>
-             <width>310</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="font">
-            <font>
-             <weight>75</weight>
-             <bold>true</bold>
-            </font>
-           </property>
-           <property name="text">
-            <string notr="true">confirm_password_label</string>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QLineEdit" name="confirm_password_line_edit">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="minimumSize">
-            <size>
-             <width>300</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>300</width>
-             <height>16777215</height>
-            </size>
-           </property>
-           <property name="echoMode">
-            <enum>QLineEdit::Password</enum>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-      </layout>
-     </item>
-     <item>
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="0" column="0">
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <property name="spacing">
+        <number>3</number>
+       </property>
+       <item>
+        <widget class="QLabel" name="reset_code">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>310</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="maximumSize">
+          <size>
+           <width>16777215</width>
+           <height>16777215</height>
+          </size>
+         </property>
+         <property name="font">
+          <font>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text">
+          <string notr="true">reset_code</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLineEdit" name="reset_code_line_edit">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>300</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="maximumSize">
+          <size>
+           <width>300</width>
+           <height>16777215</height>
+          </size>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item row="1" column="0">
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <property name="spacing">
+        <number>3</number>
+       </property>
+       <item>
+        <widget class="QLabel" name="password_label">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>310</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="maximumSize">
+          <size>
+           <width>16777215</width>
+           <height>16777215</height>
+          </size>
+         </property>
+         <property name="font">
+          <font>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text">
+          <string notr="true">password_label</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLineEdit" name="password_line_edit">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>300</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="maximumSize">
+          <size>
+           <width>300</width>
+           <height>16777215</height>
+          </size>
+         </property>
+         <property name="echoMode">
+          <enum>QLineEdit::Password</enum>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item row="2" column="0">
       <layout class="QVBoxLayout" name="verticalLayout_3">
        <property name="spacing">
-        <number>0</number>
+        <number>3</number>
        </property>
        <item>
-        <widget class="QLabel" name="password_assistance">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="minimumSize">
-          <size>
-           <width>220</width>
-           <height>100</height>
+        <widget class="QLabel" name="confirm_password_label">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>310</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="font">
+          <font>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text">
+          <string notr="true">confirm_password_label</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLineEdit" name="confirm_password_line_edit">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>300</width>
+           <height>0</height>
           </size>
          </property>
          <property name="maximumSize">
           <size>
-           <width>220</width>
+           <width>300</width>
            <height>16777215</height>
           </size>
          </property>
-         <property name="text">
-          <string notr="true">password_assistance</string>
-         </property>
-         <property name="indent">
-          <number>20</number>
+         <property name="echoMode">
+          <enum>QLineEdit::Password</enum>
          </property>
         </widget>
        </item>
-       <item>
-        <spacer name="verticalSpacer_2">
-         <property name="orientation">
-          <enum>Qt::Vertical</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>20</width>
-           <height>40</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item>
-        <spacer name="horizontalSpacer">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-         <property name="sizeType">
-          <enum>QSizePolicy::Ignored</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>220</width>
-           <height>0</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
       </layout>
      </item>
+     <item row="1" column="1" rowspan="2">
+      <widget class="QLabel" name="password_assistance">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>185</width>
+         <height>95</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>185</width>
+         <height>95</height>
+        </size>
+       </property>
+       <property name="text">
+        <string notr="true">password_assistance</string>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+       <property name="indent">
+        <number>20</number>
+       </property>
+      </widget>
+     </item>
     </layout>
    </item>
    <item>
@@ -327,22 +281,5 @@
   </layout>
  </widget>
  <resources/>
- <connections>
-  <connection>
-   <sender>confirm_password_line_edit</sender>
-   <signal>returnPressed()</signal>
-   <receiver>reset_password_button</receiver>
-   <slot>click()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>160</x>
-     <y>81</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>541</x>
-     <y>237</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
+ <connections/>
 </ui>

=== modified file 'data/qt/resources.qrc'
--- data/qt/resources.qrc	2012-02-16 14:13:36 +0000
+++ data/qt/resources.qrc	2012-03-20 16:10:09 +0000
@@ -6,5 +6,7 @@
     <file>../Ubuntu-B.ttf</file>
     <file>../balloon_shape.png</file>
     <file>stylesheet.qss</file>
+    <file>windows.qss</file>
+    <file>linux.qss</file>
   </qresource>
 </RCC>

=== modified file 'data/qt/setup_account.ui'
--- data/qt/setup_account.ui	2012-03-05 21:52:45 +0000
+++ data/qt/setup_account.ui	2012-03-20 16:10:09 +0000
@@ -85,7 +85,6 @@
         <widget class="QLabel" name="name_label">
          <property name="font">
           <font>
-           <weight>75</weight>
            <bold>true</bold>
           </font>
          </property>
@@ -114,11 +113,6 @@
            <height>16777215</height>
           </size>
          </property>
-         <property name="font">
-          <font>
-           <pointsize>11</pointsize>
-          </font>
-         </property>
          <property name="formError" stdset="0">
           <bool>false</bool>
          </property>
@@ -135,7 +129,6 @@
         <widget class="QLabel" name="email_label">
          <property name="font">
           <font>
-           <weight>75</weight>
            <bold>true</bold>
           </font>
          </property>
@@ -164,11 +157,6 @@
            <height>16777215</height>
           </size>
          </property>
-         <property name="font">
-          <font>
-           <pointsize>11</pointsize>
-          </font>
-         </property>
          <property name="placeholderText">
           <string/>
          </property>
@@ -229,7 +217,6 @@
         <widget class="QLabel" name="confirm_email_label">
          <property name="font">
           <font>
-           <weight>75</weight>
            <bold>true</bold>
           </font>
          </property>
@@ -258,11 +245,6 @@
            <height>16777215</height>
           </size>
          </property>
-         <property name="font">
-          <font>
-           <pointsize>11</pointsize>
-          </font>
-         </property>
          <property name="placeholderText">
           <string/>
          </property>
@@ -377,7 +359,6 @@
         <widget class="QLabel" name="password_label">
          <property name="font">
           <font>
-           <weight>75</weight>
            <bold>true</bold>
           </font>
          </property>
@@ -406,13 +387,8 @@
            <height>16777215</height>
           </size>
          </property>
-         <property name="font">
-          <font>
-           <pointsize>11</pointsize>
-          </font>
-         </property>
          <property name="toolTip">
-          <string notr="true">Your password must be at least 8 characters long and at least contain one number and one upper later.</string>
+          <string notr="true">Your password must be at least 8 characters long and contain at least one number and one uppercase letter.</string>
          </property>
          <property name="statusTip">
           <string/>
@@ -477,7 +453,6 @@
         <widget class="QLabel" name="confirm_password_label">
          <property name="font">
           <font>
-           <weight>75</weight>
            <bold>true</bold>
           </font>
          </property>
@@ -506,11 +481,6 @@
            <height>16777215</height>
           </size>
          </property>
-         <property name="font">
-          <font>
-           <pointsize>11</pointsize>
-          </font>
-         </property>
          <property name="echoMode">
           <enum>QLineEdit::Password</enum>
          </property>
@@ -580,11 +550,6 @@
            <height>16777215</height>
           </size>
          </property>
-         <property name="font">
-          <font>
-           <pointsize>11</pointsize>
-          </font>
-         </property>
          <property name="locale">
           <locale language="English" country="UnitedStates"/>
          </property>

=== modified file 'data/qt/ssl_dialog.ui'
--- data/qt/ssl_dialog.ui	2012-02-24 15:22:26 +0000
+++ data/qt/ssl_dialog.ui	2012-03-20 16:10:09 +0000
@@ -95,11 +95,6 @@
        </property>
        <item>
         <widget class="QLabel" name="title_label">
-         <property name="font">
-          <font>
-           <pointsize>14</pointsize>
-          </font>
-         </property>
          <property name="text">
           <string notr="true">Do you want to connect to this server</string>
          </property>

=== modified file 'data/qt/stylesheet.qss'
--- data/qt/stylesheet.qss	2012-03-05 20:30:57 +0000
+++ data/qt/stylesheet.qss	2012-03-20 16:10:09 +0000
@@ -1,5 +1,4 @@
 QWidget {
-    font-family: "Ubuntu";
     color: #333333;
 }
 
@@ -16,7 +15,6 @@
 
 QLabel#password_assistance {
     border-image: url(":/balloon_shape.png");
-    font-size: 12px;
 }
 
 QLineEdit {
@@ -91,21 +89,11 @@
     min-height: 100px;
 }
 
-QFrame#frm_box > QLabel {
-    font-size: 20px;
-}
-
-QLabel#title_label {
-    font-size: 20px;
-}
-
-QFrame#header {
+WizardHeader {
     padding-top: 1px;
     padding-bottom: 1px;
 }
 
 QLabel#form_errors {
-    font: bold 14px;
-    color: #df2d1f;
     padding-bottom: 1px;
 }

=== added file 'data/qt/windows.qss'
--- data/qt/windows.qss	1970-01-01 00:00:00 +0000
+++ data/qt/windows.qss	2012-03-20 16:10:09 +0000
@@ -0,0 +1,5 @@
+/* Styles specific to the windows platform */
+
+QWidget {
+    font-family: "Ubuntu";
+}

=== modified file 'po/POTFILES.in'
--- po/POTFILES.in	2010-11-19 21:35:11 +0000
+++ po/POTFILES.in	2012-03-20 16:10:09 +0000
@@ -1,1 +1,2 @@
 ubuntu_sso/gtk/gui.py
+ubuntu_sso/utils/ui.py

=== modified file 'run-tests'
--- run-tests	2012-02-17 16:57:34 +0000
+++ run-tests	2012-03-20 16:10:09 +0000
@@ -57,7 +57,7 @@
 
 echo "*** Running QT test suite for ""$MODULE"" ***"
 ./setup.py build
-USE_QT_MAINLOOP=True $XVFB_CMDLINE u1trial --reactor=qt4 --gui -p "$GTK_TESTS_PATH" -i "test_windows.py" "$MODULE"
+$XVFB_CMDLINE u1trial --reactor=qt4 --gui -p "$GTK_TESTS_PATH" -i "test_windows.py" "$MODULE"
 rm -rf _trial_temp
 rm -rf build
 

=== modified file 'ubuntu_sso/__init__.py'
--- ubuntu_sso/__init__.py	2012-02-11 19:25:01 +0000
+++ ubuntu_sso/__init__.py	2012-03-20 16:10:09 +0000
@@ -15,6 +15,8 @@
 # with this program.  If not, see <http://www.gnu.org/licenses/>.
 """Ubuntu Single Sign On client code."""
 
+import sys
+
 # DBus constants
 DBUS_BUS_NAME = "com.ubuntu.sso"
 
@@ -29,7 +31,13 @@
 # return codes for UIs
 USER_SUCCESS = 0
 USER_CANCELLATION = 10
+EXCEPTION_RAISED = 11
 
 # available UIs
 UI_EXECUTABLE_GTK = 'ubuntu-sso-login-gtk'
 UI_EXECUTABLE_QT = 'ubuntu-sso-login-qt'
+UI_PROXY_CREDS_DIALOG = 'ubuntu-sso-proxy-creds-qt'
+
+if getattr(sys, "frozen", None) is not None and sys.platform == "win32":
+    UI_EXECUTABLE_QT += ".exe"
+    UI_PROXY_CREDS_DIALOG += ".exe"

=== modified file 'ubuntu_sso/gtk/gui.py'
--- ubuntu_sso/gtk/gui.py	2012-02-17 18:43:17 +0000
+++ ubuntu_sso/gtk/gui.py	2012-03-20 16:10:09 +0000
@@ -90,23 +90,6 @@
     return c
 # pylint: enable=C0103
 
-
-# To be removed when Python bindings provide these constants
-# as per http://code.google.com/p/pywebkitgtk/issues/detail?id=44
-# WebKitLoadStatus
-WEBKIT_LOAD_PROVISIONAL = 0
-WEBKIT_LOAD_COMMITTED = 1
-WEBKIT_LOAD_FINISHED = 2
-WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT = 3
-WEBKIT_LOAD_FAILED = 4
-# WebKitWebNavigationReason
-WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED = 0
-WEBKIT_WEB_NAVIGATION_REASON_FORM_SUBMITTED = 1
-WEBKIT_WEB_NAVIGATION_REASON_BACK_FORWARD = 2
-WEBKIT_WEB_NAVIGATION_REASON_RELOAD = 3
-WEBKIT_WEB_NAVIGATION_REASON_FORM_RESUBMITTED = 4
-WEBKIT_WEB_NAVIGATION_REASON_OTHER = 5
-
 DEFAULT_WIDTH = 30
 # To be replaced by values from the theme (LP: #616526)
 HELP_TEXT_COLOR = parse_color("#bfbfbf")
@@ -947,23 +930,14 @@
 
         self._set_current_page(self.processing_vbox)
 
-    def on_tc_button_clicked(self, *args, **kwargs):
-        """The T&C button was clicked, create the browser and load terms."""
+    def _add_webkit_browser(self):
+        """Add the webkit browser for the t&c."""
         # delay the import of webkit to be able to build without it
         from gi.repository import WebKit  # pylint: disable=E0611
+
         browser = WebKit.WebView()
 
-        # The signal WebKitWebView::load-finished is deprecated and should not
-        # be used in newly-written code. Use the "load-status" property
-        # instead. Connect to "notify::load-status" to monitor loading.
-
-        # nataliabidart (2010-10-04): connecting this signal makes the loading
-        # of the Ubuntu One terms URL to fail. So we're using the deprecated
-        # 'load-finished' for now.
-
-        #browser.connect('notify::load-status',
-        #                self.on_tc_browser_notify_load_status)
-        browser.connect('load-finished',
+        browser.connect('notify::load-status',
                         self.on_tc_browser_notify_load_status)
         browser.connect('navigation-policy-decision-requested',
                         self.on_tc_browser_navigation_requested)
@@ -978,7 +952,14 @@
         browser.load_uri(self.tc_url)
         browser.show()
         self.tc_browser_window.add(browser)
-        self._set_current_page(self.processing_vbox)
+
+    def on_tc_button_clicked(self, *args, **kwargs):
+        """The T&C button was clicked, create the browser and load terms."""
+        if self.tc_browser_window.get_child() is None:
+            self._add_webkit_browser()
+            self._set_current_page(self.processing_vbox)
+        else:
+            self._set_current_page(self.tc_browser_vbox)
 
     def on_tc_back_button_clicked(self, *args, **kwargs):
         """T & C 'back' button was clicked, return to the previous page."""
@@ -986,14 +967,18 @@
 
     def on_tc_browser_notify_load_status(self, browser, *args, **kwargs):
         """The T&C page is being loaded."""
-        if browser.get_load_status() == WEBKIT_LOAD_FINISHED:
+        from gi.repository import WebKit  # pylint: disable=E0611
+
+        if browser.get_load_status().real == WebKit.LoadStatus.FINISHED:
             self._set_current_page(self.tc_browser_vbox)
 
     def on_tc_browser_navigation_requested(self, browser, frame, request,
                                            action, decision, *args, **kwargs):
         """The user wants to navigate within the T&C browser."""
+        from gi.repository import WebKit  # pylint: disable=E0611
+
         if action is not None and \
-           action.get_reason() == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED:
+           action.get_reason() == WebKit.WebNavigationReason.LINK_CLICKED:
             if decision is not None:
                 decision.ignore()
             url = action.get_original_uri()

=== modified file 'ubuntu_sso/gtk/tests/test_gui.py'
--- ubuntu_sso/gtk/tests/test_gui.py	2012-02-17 18:43:17 +0000
+++ ubuntu_sso/gtk/tests/test_gui.py	2012-03-20 16:10:09 +0000
@@ -134,7 +134,7 @@
 
     def get_load_status(self):
         """Return the current load status."""
-        return gui.WEBKIT_LOAD_FINISHED
+        return WebKit.LoadStatus.FINISHED
 
     def show(self):
         """Show this instance."""
@@ -981,7 +981,7 @@
     def test_notify_load_finished_connected(self):
         """The 'load-finished' signal is connected."""
         expected = [self.ui.on_tc_browser_notify_load_status]
-        self.assertEqual(self.browser._signals['load-finished'],
+        self.assertEqual(self.browser._signals['notify::load-status'],
                          expected)
 
     def test_tc_loaded_morphs_into_tc_browser_vbox(self):
@@ -998,7 +998,7 @@
     def test_navigation_requested_succeeds_for_no_clicking(self):
         """The navigation request succeeds when user hasn't clicked a link."""
         action = WebKit.WebNavigationAction()
-        action.set_reason(gui.WEBKIT_WEB_NAVIGATION_REASON_OTHER)
+        action.set_reason(WebKit.WebNavigationReason.OTHER)
 
         decision = WebKit.WebPolicyDecision()
         decision.use = self._set_called
@@ -1011,7 +1011,7 @@
     def test_navigation_requested_ignores_clicked_links(self):
         """The navigation request is ignored if a link was clicked."""
         action = WebKit.WebNavigationAction()
-        action.set_reason(gui.WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED)
+        action.set_reason(WebKit.WebNavigationReason.LINK_CLICKED)
 
         decision = WebKit.WebPolicyDecision()
         decision.ignore = self._set_called
@@ -1037,7 +1037,7 @@
         """
         url = 'http://something.com/yadda'
         action = WebKit.WebNavigationAction()
-        action.set_reason(gui.WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED)
+        action.set_reason(WebKit.WebNavigationReason.LINK_CLICKED)
         action.set_original_uri(url)
 
         decision = WebKit.WebPolicyDecision()
@@ -1050,6 +1050,35 @@
         self.ui.on_tc_browser_navigation_requested(**kwargs)
         self.assertEqual(self._called, ((url,), {}))
 
+    def test_on_tc_button_clicked_no_child(self):
+        """Test the tc loading with no child."""
+        called = []
+
+        def fake_add_browser():
+            """Fake add browser."""
+            called.append('fake_add_browser')
+
+        self.patch(self.ui, '_add_webkit_browser', fake_add_browser)
+        self.patch(self.ui.tc_browser_window, 'get_child', lambda: None)
+
+        self.ui.on_tc_button_clicked()
+        self.assertIn('fake_add_browser', called)
+
+    def test_on_tc_button_clicked_child(self):
+        """Test the tc loading with child."""
+        called = []
+
+        def fake_add_browser(i_self):
+            """Fake add browser."""
+            called.append('fake_add_browser')
+
+        self.patch(self.ui, '_add_webkit_browser', fake_add_browser)
+
+        browser = WebKit.WebView()
+        self.ui.tc_browser_window.add(browser)
+        self.ui.on_tc_button_clicked()
+        self.assertNotIn('fake_add_browser', called)
+
 
 class RegistrationErrorTestCase(UbuntuSSOClientTestCase):
     """Test suite for the user registration error handling."""

=== modified file 'ubuntu_sso/qt/__init__.py'
--- ubuntu_sso/qt/__init__.py	2012-03-05 18:56:50 +0000
+++ ubuntu_sso/qt/__init__.py	2012-03-20 16:10:09 +0000
@@ -18,19 +18,27 @@
 
 import collections
 
-
-LINK_STYLE = ('<a href="{link_url}">'
+from PyQt4 import QtGui, QtCore
+
+from ubuntu_sso.logger import setup_gui_logging
+from ubuntu_sso.utils.ui import GENERIC_BACKEND_ERROR
+
+logger = setup_gui_logging('ubuntu_sso.qt')
+
+LINK_STYLE = (u'<a href="{link_url}">'
               '<span style="color:#df2d1f;">{link_text}</span></a>')
 ERROR_ALL = '__all__'
-ERROR_STYLE = u'<font color="#df2d1f"><b>%s</b></font>'
+ERROR_STYLE = u'<font color="#df2d1f" style="font-size:small"><b>%s</b></font>'
 ERROR_MESSAGE = 'message'
 PREFERED_UI_SIZE = {'width': 550, 'height': 525}
-TITLE_STYLE = u'<span style="font-size:24px">%s</span>'
+TITLE_STYLE = u'<span style="font-size:xx-large;font-weight:bold;">%s</span>'
+WINDOW_TITLE = 'Ubuntu Single Sign On'
 
 
 # Based on the gtk implementation
 def build_general_error_message(errordict):
     """Build a user-friendly error message from the errordict."""
+    logger.debug('build_general_error_message: errordict is: %r.', errordict)
     result = ''
     if isinstance(errordict, collections.Mapping):
         msg1 = errordict.get(ERROR_ALL)
@@ -50,5 +58,25 @@
             result = '\n'.join(
                 [('%s: %s' % (k, v)) for k, v in errordict.iteritems()])
     else:
-        result = repr(errordict)
+        result = GENERIC_BACKEND_ERROR
+        logger.error('build_general_error_message with unknown error: %r',
+            errordict)
+
+    logger.info('build_general_error_message: returning %r.', result)
     return result
+
+
+def maybe_elide_text(label, text, width, markup=None):
+    """Set 'text' to be the 'label's text.
+
+    If 'text' is longer than 'width', set the label's tooltip to be the full
+    text, and the text itself to be the elided version of 'text'.
+
+    """
+    fm = QtGui.QFontMetrics(label.font())
+    elided_text = fm.elidedText(text, QtCore.Qt.ElideRight, width)
+    if elided_text != text:
+        label.setToolTip(text)
+    if markup is not None:
+        elided_text = markup % elided_text
+    label.setText(elided_text)

=== modified file 'ubuntu_sso/qt/common.py'
--- ubuntu_sso/qt/common.py	2012-02-16 14:13:36 +0000
+++ ubuntu_sso/qt/common.py	2012-03-20 16:10:09 +0000
@@ -28,9 +28,9 @@
 )
 
 # all the text + styles that are used in the gui
-BAD = u'<img src=":/password_hint_warning.png" /><font> %s </font>'
-GOOD = u'<img src=":/password_hint_ok.png" /><font> %s </font>'
-NORMAL = u'<font> %s </font>'
+BAD = u'<img src=":/password_hint_warning.png" /><small> %s </small>'
+GOOD = u'<img src=":/password_hint_ok.png" /><small> %s </small>'
+NORMAL = u'<small> %s </small>'
 
 
 def password_assistance(line_edit, assistance, icon_type=BAD):

=== modified file 'ubuntu_sso/qt/current_user_sign_in_page.py'
--- ubuntu_sso/qt/current_user_sign_in_page.py	2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/current_user_sign_in_page.py	2012-03-20 16:10:09 +0000
@@ -61,6 +61,11 @@
          }
         return result
 
+    @property
+    def password(self):
+        """Return the content of the password edit."""
+        return unicode(self.ui.password_edit.text())
+
     def on_user_not_validated(self, app_name, email):
         """Show the validate email page."""
         self.hide_overlay()
@@ -76,6 +81,7 @@
 
     def initializePage(self):
         """Setup UI details."""
+        logger.debug('initializePage - About to show CurrentUserSignInPage')
         self.setButtonText(QtGui.QWizard.CancelButton, CANCEL_BUTTON)
         # Layout without custom button 1,
         # without finish button
@@ -94,10 +100,8 @@
 
     def _set_translated_strings(self):
         """Set the translated strings."""
-        logger.debug('CurrentUserSignInPage._set_translated_strings')
         self.setTitle(LOGIN_TITLE.format(app_name=self.app_name))
         self.setSubTitle(LOGIN_SUBTITLE % {'app_name': self.app_name})
-
         self.ui.email_label.setText(EMAIL_LABEL)
         self.ui.password_label.setText(LOGIN_PASSWORD_LABEL)
         forgotten_text = LINK_STYLE.format(link_url='#',
@@ -107,7 +111,6 @@
 
     def _connect_ui(self):
         """Connect the buttons to perform actions."""
-        logger.debug('CurrentUserSignInPage._connect_buttons')
         self.ui.forgot_password_label.linkActivated.connect(
                                                     self.on_forgotten_password)
         self.ui.email_edit.textChanged.connect(self._validate)
@@ -116,18 +119,16 @@
 
     def _validate(self):
         """Perform input validation."""
-        valid = True
         correct_mail = is_correct_email(unicode(self.ui.email_edit.text()))
-        password = unicode(self.ui.password_edit.text())
-        if not correct_mail or not password:
-            valid = False
-        self.ui.sign_in_button.setEnabled(valid)
+        correct_password = len(unicode(self.ui.password_edit.text())) > 0
+        enabled = correct_mail and correct_password
+        self.ui.sign_in_button.setEnabled(enabled)
 
     def login(self):
         """Perform the login using the self.backend."""
-        logger.debug('CurrentUserSignInPage.login')
         # grab the data from the view and call the backend
         email = unicode(self.ui.email_edit.text())
+        logger.info('CurrentUserSignInPage.login for: %s', email)
         password = unicode(self.ui.password_edit.text())
         args = (self.app_name, email, password)
         if self.ping_url:
@@ -146,18 +147,18 @@
         # let the user know
         logger.error('Got error when login %s, error: %s',
             self.app_name, error)
-        self.show_error(self.app_name, build_general_error_message(error))
+        self.show_error(build_general_error_message(error))
 
     def on_logged_in(self, app_name, result):
         """We managed to log in."""
         logger.info('Logged in for %s', app_name)
         self.hide_overlay()
         email = unicode(self.ui.email_edit.text())
+        logger.debug('About to emit userLoggedIn signal with: (%s).', email)
         self.userLoggedIn.emit(email)
-        logger.debug('Wizard.loginSuccess emitted.')
 
     def on_forgotten_password(self, link=None):
         """Show the user the forgotten password page."""
-        logger.info('Forgotten password')
         self.hide_overlay()
+        logger.debug('About to emit passwordForgotten signal')
         self.passwordForgotten.emit()

=== modified file 'ubuntu_sso/qt/email_verification_page.py'
--- ubuntu_sso/qt/email_verification_page.py	2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/email_verification_page.py	2012-03-20 16:10:09 +0000
@@ -71,7 +71,6 @@
 
     def _connect_ui(self):
         """Set the connection of signals."""
-        logger.debug('EmailVerificationController._connect_ui')
         self.ui.verification_code_edit.textChanged.connect(
             self.validate_form)
         self.next_button.clicked.connect(self.validate_email)
@@ -84,7 +83,6 @@
 
     def _set_translated_strings(self):
         """Set the different titles."""
-        logger.debug('EmailVerificationController._set_titles')
         self.header.set_title(VERIFY_EMAIL_TITLE)
         self.header.set_subtitle(VERIFY_EMAIL_CONTENT % {
             "app_name": self.app_name,
@@ -103,7 +101,8 @@
 
     def validate_email(self):
         """Call the next action."""
-        logger.debug('EmailVerificationController.validate_email')
+        logger.debug('EmailVerificationController.validate_email for: %s',
+            self.email)
         code = unicode(self.ui.verification_code_edit.text())
         args = (self.app_name, self.email, self.password, code)
         self.hide_error()
@@ -123,21 +122,25 @@
 
     def on_email_validated(self, app_name, email):
         """Signal thrown after the email is validated."""
-        logger.info('EmailVerificationController.on_email_validated')
+        logger.info('EmailVerificationController.on_email_validated for %s, '
+                    'email: %s', app_name, email)
         self.hide_overlay()
         self.registrationSuccess.emit(self.email)
 
     def on_email_validation_error(self, app_name, error):
         """Signal thrown when there's a problem validating the email."""
+        logger.error('Got error on email validation %s, error: %s',
+            app_name, error)
         self.hide_overlay()
         msg = error.pop(ERROR_EMAIL_TOKEN, '')
         msg += build_general_error_message(error)
-        self.show_error(self.app_name, msg)
+        self.show_error(msg)
 
     # pylint: disable=C0103
 
     def initializePage(self):
         """Called to prepare the page just before it is shown."""
+        logger.debug('initializePage - About to show EmailVerificationPage')
         self.next_button.setDefault(True)
         self.next_button.setEnabled(False)
         self.wizard().setButtonLayout([QtGui.QWizard.Stretch])

=== modified file 'ubuntu_sso/qt/enhanced_check_box.py'
--- ubuntu_sso/qt/enhanced_check_box.py	2012-03-01 16:53:29 +0000
+++ ubuntu_sso/qt/enhanced_check_box.py	2012-03-20 16:10:09 +0000
@@ -24,11 +24,12 @@
 class EnhancedCheckBox(QtGui.QCheckBox):
     """Enhanced QCheckBox to support links in the message displayed."""
 
-    def __init__(self, text=""):
-        QtGui.QCheckBox.__init__(self)
+    def __init__(self, text="", parent=None):
+        QtGui.QCheckBox.__init__(self, parent)
         hbox = QtGui.QHBoxLayout()
+        hbox.setAlignment(QtCore.Qt.AlignLeft)
         self.text_label = QtGui.QLabel(text)
-        self.text_label.setAlignment(QtCore.Qt.AlignTop)
+        self.text_label.setWordWrap(True)
         self.text_label.setOpenExternalLinks(True)
         padding = self.iconSize().width()
         self.text_label.setStyleSheet("margin-top: -3px;"
@@ -37,6 +38,11 @@
         hbox.addWidget(self.text_label)
         self.setLayout(hbox)
 
+        if parent is not None:
+            lines = self.text_label.width() / float(parent.width())
+            self.text_label.setMinimumWidth(parent.width())
+            self.setMinimumHeight(self.height() * lines)
+
         self.stateChanged.connect(self.text_label.setFocus)
 
     def text(self):

=== modified file 'ubuntu_sso/qt/forgotten_password_page.py'
--- ubuntu_sso/qt/forgotten_password_page.py	2012-03-05 20:31:22 +0000
+++ ubuntu_sso/qt/forgotten_password_page.py	2012-03-20 16:10:09 +0000
@@ -63,6 +63,7 @@
 
     def initializePage(self):
         """Set the initial state of ForgottenPassword page."""
+        logger.debug('initializePage - About to show ForgottenPasswordPage')
         self.ui.send_button.setDefault(True)
         enabled = not self.ui.email_line_edit.text().isEmpty()
         self.ui.send_button.setEnabled(enabled)
@@ -98,6 +99,7 @@
         """Send the request password operation."""
         self.hide_error()
         args = (self.app_name, self.email_address)
+        logger.debug('Sending request new password for %s, email: %s', *args)
         f = self.backend.request_password_reset_token
 
         error_handler = partial(self._handle_error, f,
@@ -113,6 +115,8 @@
 
     def on_password_reset_token_sent(self, app_name, email):
         """Action taken when we managed to get the password reset done."""
+        logger.info('ForgottenPasswordPage.on_password_reset_token_sent for '
+                    '%s, email: %s', app_name, email)
         # ignore the result and move to the reset page
         self.hide_overlay()
         self.passwordResetTokenSent.emit(email)
@@ -123,4 +127,4 @@
         # set the error message
         self.hide_overlay()
         msg = REQUEST_PASSWORD_TOKEN_WRONG_EMAIL
-        self.show_error(self.app_name, msg)
+        self.show_error(msg)

=== modified file 'ubuntu_sso/qt/loadingoverlay.py'
--- ubuntu_sso/qt/loadingoverlay.py	2012-02-27 19:00:59 +0000
+++ ubuntu_sso/qt/loadingoverlay.py	2012-03-20 16:10:09 +0000
@@ -21,6 +21,8 @@
 from ubuntu_sso.qt.ui import loadingoverlay_ui
 from ubuntu_sso.utils.ui import LOADING_OVERLAY
 
+LOADING_STYLE = u'<span style="font-size:x-large;">{0}</span>'
+
 
 class LoadingOverlay(QtGui.QFrame):
     """The widget that shows a loading animation and disable the widget below.
@@ -43,7 +45,7 @@
         self.counter = 0
         self.orientation = False
 
-        self.ui.label.setText(LOADING_OVERLAY)
+        self.ui.label.setText(LOADING_STYLE.format(LOADING_OVERLAY))
 
     # Invalid name "paintEvent", "eventFilter", "showEvent", "timerEvent"
     # pylint: disable=C0103

=== modified file 'ubuntu_sso/qt/main.py'
--- ubuntu_sso/qt/main.py	2012-02-24 19:54:48 +0000
+++ ubuntu_sso/qt/main.py	2012-03-20 16:10:09 +0000
@@ -25,6 +25,7 @@
 from ubuntu_sso.qt.ui import resources_rc
 # pylint: enable=W0611
 from ubuntu_sso.qt.ubuntu_sso_wizard import UbuntuSSOClientGUI
+from ubuntu_sso.utils import PLATFORM_QSS
 
 
 def main(**kwargs):
@@ -34,9 +35,17 @@
     QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-R.ttf')
     QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-B.ttf')
 
-    # Apply Style Sheet -- The windows version may be different
-    qss = QtCore.QResource(":/stylesheet.qss")
-    app.setStyleSheet(qss.data())
+    data = []
+    for qss_name in (PLATFORM_QSS, ":/stylesheet.qss"):
+        qss = QtCore.QResource(qss_name)
+        data.append(unicode(qss.data()))
+    app.setStyleSheet('\n'.join(data))
+
+    # Fix the string that contains unicode chars.
+    for key in kwargs:
+        value = kwargs[key]
+        if isinstance(value, str):
+            kwargs[key] = value.decode('utf-8')
 
     # Unused variable 'ui', pylint: disable=W0612
     ui = UbuntuSSOClientGUI(close_callback=app.exit, **kwargs)

=== modified file 'ubuntu_sso/qt/network_detection_page.py'
--- ubuntu_sso/qt/network_detection_page.py	2012-02-29 14:00:12 +0000
+++ ubuntu_sso/qt/network_detection_page.py	2012-03-20 16:10:09 +0000
@@ -20,7 +20,7 @@
 from PyQt4 import QtGui
 
 from ubuntu_sso import networkstate
-
+from ubuntu_sso.logger import setup_logging
 from ubuntu_sso.qt.sso_wizard_page import SSOWizardPage
 from ubuntu_sso.qt.ui import network_detection_ui
 from ubuntu_sso.utils.ui import (
@@ -31,6 +31,9 @@
 )
 
 
+logger = setup_logging('ubuntu_sso.network_detection_page')
+
+
 class NetworkDetectionPage(SSOWizardPage):
 
     """Widget to show if we don't detect a network connection."""
@@ -48,6 +51,7 @@
 
     def initializePage(self):
         """Set UI details."""
+        logger.debug('initializePage - About to show NetworkDetectionPage')
         self.wizard()._next_id = None
 
         self.setButtonText(QtGui.QWizard.CustomButton1, TRY_AGAIN_BUTTON)

=== modified file 'ubuntu_sso/qt/proxy_dialog.py'
--- ubuntu_sso/qt/proxy_dialog.py	2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/proxy_dialog.py	2012-03-20 16:10:09 +0000
@@ -21,6 +21,7 @@
 from PyQt4.QtGui import QApplication, QDialog, QIcon
 from twisted.internet import defer
 
+from ubuntu_sso import EXCEPTION_RAISED, USER_SUCCESS, USER_CANCELLATION
 from ubuntu_sso.logger import setup_gui_logging
 from ubuntu_sso.keyring import Keyring
 from ubuntu_sso.qt.ui.proxy_credentials_dialog_ui import Ui_ProxyCredsDialog
@@ -37,10 +38,6 @@
     PROXY_CREDS_SAVE_BUTTON,
 )
 
-CREDS_ACQUIRED = 0
-USER_CANCELATION = -1
-EXCEPTION_RAISED = -2
-
 logger = setup_gui_logging("ubuntu_sso.qt.proxy_dialog")
 
 
@@ -107,15 +104,15 @@
             logger.debug('Save credentials as for domain %s.', self.domain)
             yield self.keyring.set_credentials(self.domain, creds)
         except Exception, e:
-            logger.error('Could not retrieve credentials.')
+            logger.exception('Could not set credentials:')
             self.done(EXCEPTION_RAISED)
         # pylint: disable=W0703, W0612
-        self.done(CREDS_ACQUIRED)
+        self.done(USER_SUCCESS)
 
     def _on_cancel_clicked(self, *args):
         """End the dialog."""
         logger.debug('User canceled credentials dialog.')
-        self.done(USER_CANCELATION)
+        self.done(USER_CANCELLATION)
 
     def _set_buttons(self):
         """Set the labels of the buttons."""

=== modified file 'ubuntu_sso/qt/reset_password_page.py'
--- ubuntu_sso/qt/reset_password_page.py	2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/reset_password_page.py	2012-03-20 16:10:09 +0000
@@ -18,7 +18,7 @@
 
 from functools import partial
 
-from PyQt4.QtCore import SIGNAL, pyqtSignal
+from PyQt4.QtCore import Qt, SIGNAL, pyqtSignal
 from PyQt4.QtGui import QApplication
 
 from ubuntu_sso import NO_OP
@@ -73,7 +73,9 @@
 
     def initializePage(self):
         """Extends QWizardPage initializePage method."""
+        logger.debug('initializePage - About to show ResetPasswordPage')
         super(ResetPasswordPage, self).initializePage()
+        self.ui.gridLayout.setAlignment(Qt.AlignLeft)
         common.password_default_assistance(self.ui.password_assistance)
         self.ui.password_assistance.setVisible(False)
         self.setTitle(RESET_TITLE)
@@ -158,6 +160,8 @@
 
     def on_password_changed(self, app_name, email):
         """Let user know that the password was changed."""
+        logger.info('ResetPasswordPage.on_password_changed for %s, email: %s',
+            app_name, email)
         self.hide_overlay()
         email = unicode(self.wizard().forgotten.ui.email_line_edit.text())
         self.passwordChanged.emit(email)
@@ -166,7 +170,7 @@
         """Let the user know that there was an error."""
         logger.error('Got error changing password for %s, error: %s',
                      self.app_name, error)
-        self.show_error(self.app_name, build_general_error_message(error))
+        self.show_error(build_general_error_message(error))
 
     def set_new_password(self):
         """Request a new password to be set."""

=== modified file 'ubuntu_sso/qt/setup_account_page.py'
--- ubuntu_sso/qt/setup_account_page.py	2012-03-06 14:13:10 +0000
+++ ubuntu_sso/qt/setup_account_page.py	2012-03-20 16:10:09 +0000
@@ -31,7 +31,7 @@
 from PyQt4 import QtGui, QtCore
 
 from ubuntu_sso import NO_OP
-from ubuntu_sso.logger import setup_gui_logging
+from ubuntu_sso.logger import setup_gui_logging, log_call
 from ubuntu_sso.qt import (
     LINK_STYLE,
     build_general_error_message,
@@ -112,11 +112,17 @@
         }
         return result
 
+    @property
+    def password(self):
+        """Return the content of the password edit."""
+        return unicode(self.ui.password_edit.text())
+
     # Invalid name "initializePage"
     # pylint: disable=C0103
 
     def initializePage(self):
         """Setup UI details."""
+        logger.debug('initializePage - About to show SetupAccountPage')
         # Set Setup Account button
         self.wizard().setOption(QtGui.QWizard.HaveCustomButton3, True)
         try:
@@ -152,7 +158,6 @@
 
     def _set_translated_strings(self):
         """Set the strings."""
-        logger.debug('SetUpAccountPage._set_translated_strings')
         # set the translated string
         title_page = REGISTER_TITLE.format(app_name=self.app_name)
         self.setTitle(title_page)
@@ -190,7 +195,7 @@
             terms = AGREE_TO_PRIVACY_POLICY.format(app_name=self.app_name,
                         privacy_policy=privacy_policy_link)
 
-        self.terms_checkbox = enhanced_check_box.EnhancedCheckBox(terms)
+        self.terms_checkbox = enhanced_check_box.EnhancedCheckBox(terms, self)
         self.ui.hlayout_check.addWidget(self.terms_checkbox)
         self.terms_checkbox.setVisible(bool(self.tc_url or self.policy_url))
 
@@ -220,7 +225,6 @@
 
     def _connect_ui(self):
         """Set the connection of signals."""
-        logger.debug('SetUpAccountPage._connect_ui')
         self._set_line_edits_validations()
 
         self.ui.captcha_view.setPixmap(QtGui.QPixmap())
@@ -229,6 +233,7 @@
                                                  self.ui.password_assistance,
                                                  common.NORMAL))
 
+        self.ui.refresh_label.linkActivated.connect(self.hide_error)
         self.ui.refresh_label.linkActivated.connect(lambda url: \
                                           self._refresh_captcha())
         # We need to check if we enable the button on many signals
@@ -271,7 +276,6 @@
     def _refresh_captcha(self):
         """Refresh the captcha image shown in the ui."""
         logger.debug('SetUpAccountPage._refresh_captcha')
-        self.hide_error()
         # lets clean behind us, do we have the old file arround?
         if self.captcha_file and os.path.exists(self.captcha_file):
             os.unlink(self.captcha_file)
@@ -297,11 +301,9 @@
         self.registerField('email_address', self.ui.email_edit)
         self.registerField('password', self.ui.password_edit)
 
+    @log_call(logger.debug)
     def on_captcha_generated(self, app_name, result):
         """A new image was generated."""
-        logger.debug('SetUpAccountPage.on_captcha_generated for %r '
-                     '(captcha id %r, filename %r).',
-                     app_name, result, self.captcha_file)
         self.captcha_id = result
         # HACK: First, let me apologize before hand, you can mention my mother
         # if needed I would do the same (mandel)
@@ -320,28 +322,28 @@
         self.captcha_image = pixmap_image
         self.on_captcha_refresh_complete()
 
-    def on_captcha_generation_error(self, error, *args, **kwargs):
+    @log_call(logger.error)
+    def on_captcha_generation_error(self, app_name, error):
         """An error ocurred."""
-        logger.debug('SetUpAccountPage.on_captcha_generation_error')
-        self.show_error(self.app_name, CAPTCHA_LOAD_ERROR)
+        self.show_error(CAPTCHA_LOAD_ERROR)
         self.on_captcha_refresh_complete()
 
+    @log_call(logger.error)
     def on_user_registration_error(self, app_name, error):
         """Let the user know we could not register."""
-        logger.debug('SetUpAccountPage.on_user_registration_error')
         # errors are returned as a dict with the data we want to show.
         msg = error.pop(ERROR_EMAIL, '')
         if msg:
             self.set_error_message(self.ui.email_assistance, msg)
         error_msg = build_general_error_message(error)
         if error_msg:
-            self.show_error(self.app_name, error_msg)
+            self.show_error(error_msg)
         self._refresh_captcha()
 
+    @log_call(logger.info)
     def on_user_registered(self, app_name, email):
         """Execute when the user did register."""
         self.hide_overlay()
-        logger.debug('SetUpAccountPage.on_user_registered')
         email = unicode(self.ui.email_edit.text())
         self.userRegistered.emit(email)
 
@@ -376,7 +378,7 @@
             messages.append(CAPTCHA_REQUIRED_ERROR)
         if len(messages) > 0:
             condition = False
-            self.show_error(self.app_name, '\n'.join(messages))
+            self.show_error('\n'.join(messages))
         return condition
 
     def set_next_validation(self):
@@ -401,17 +403,14 @@
 
     def is_correct_email(self, email_address):
         """Return if the email is correct."""
-        logger.debug('SetUpAccountPage.is_correct_email')
         return '@' in email_address
 
     def is_correct_email_confirmation(self, email_address):
         """Return that the email is the same."""
-        logger.debug('SetUpAccountPage.is_correct_email_confirmation')
         return unicode(self.ui.email_edit.text()) == email_address
 
     def is_correct_password_confirmation(self, password):
         """Return that the passwords are correct."""
-        logger.debug('SetUpAccountPage.is_correct_password_confirmation')
         return unicode(self.ui.password_edit.text()) == password
 
     def focus_changed(self, old, now):
@@ -500,12 +499,14 @@
 
     def on_captcha_refreshing(self):
         """Show overlay when captcha is refreshing."""
+        logger.info('SetUpAccountPage.on_captcha_refreshing')
         if self.isVisible():
             self.show_overlay()
         self.captcha_received = False
 
     def on_captcha_refresh_complete(self):
         """Hide overlay when captcha finished refreshing."""
+        logger.info('SetUpAccountPage.on_captcha_refresh_complete')
         self.hide_overlay()
         self.captcha_received = True
 

=== modified file 'ubuntu_sso/qt/sso_wizard_page.py'
--- ubuntu_sso/qt/sso_wizard_page.py	2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/sso_wizard_page.py	2012-03-20 16:10:09 +0000
@@ -23,31 +23,38 @@
     QApplication,
     QCursor,
     QFrame,
-    QFontMetrics,
     QHBoxLayout,
+    QLabel,
+    QStyle,
     QVBoxLayout,
-    QStyle,
     QWizardPage,
-    QLabel,
 )
 from twisted.internet import defer
 
 from ubuntu_sso import main
-from ubuntu_sso.logger import setup_gui_logging
-from ubuntu_sso.qt import PREFERED_UI_SIZE, TITLE_STYLE
+from ubuntu_sso.logger import setup_gui_logging, log_call
+from ubuntu_sso.qt import (
+    ERROR_STYLE,
+    maybe_elide_text,
+    PREFERED_UI_SIZE,
+    TITLE_STYLE,
+)
 from ubuntu_sso.utils.ui import GENERIC_BACKEND_ERROR
 
 
 logger = setup_gui_logging('ubuntu_sso.sso_wizard_page')
 
 
-class Header(QFrame):
-    """Header Class for Title and Subtitle in all wizard pages."""
+class WizardHeader(QFrame):
+    """WizardHeader Class for Title and Subtitle in all wizard pages."""
 
-    def __init__(self):
+    def __init__(self, max_width, parent=None):
         """Create a new instance."""
-        super(Header, self).__init__()
-        self.setObjectName('header')
+        super(WizardHeader, self).__init__(parent=parent)
+        self.max_width = max_width
+        self.max_title_width = self.max_width * 0.95
+        self.max_subtitle_width = self.max_width * 1.8
+
         vbox = QVBoxLayout(self)
         vbox.setContentsMargins(0, 0, 0, 0)
         self.title_label = QLabel()
@@ -64,11 +71,8 @@
     def set_title(self, title):
         """Set the Title of the page or hide it otherwise"""
         if title:
-            fm = QFontMetrics(self.subtitle_label.font())
-            width = PREFERED_UI_SIZE['width'] * 0.95
-            elided_text = fm.elidedText(title, Qt.ElideRight, width)
-            self.title_label.setToolTip(title)
-            self.title_label.setText(elided_text)
+            maybe_elide_text(self.title_label, title, self.max_title_width,
+                             markup=TITLE_STYLE)
             self.title_label.setVisible(True)
         else:
             self.title_label.setVisible(False)
@@ -76,28 +80,23 @@
     def set_subtitle(self, subtitle):
         """Set the Subtitle of the page or hide it otherwise"""
         if subtitle:
-            fm = QFontMetrics(self.subtitle_label.font())
-            width = PREFERED_UI_SIZE['width'] * 1.8
-            elided_text = fm.elidedText(subtitle, Qt.ElideRight, width)
-            self.subtitle_label.setText(elided_text)
-            self.subtitle_label.setToolTip(subtitle)
+            maybe_elide_text(self.subtitle_label, subtitle,
+                             self.max_subtitle_width)
             self.subtitle_label.setVisible(True)
         else:
             self.subtitle_label.setVisible(False)
 
 
-class SSOWizardPage(QWizardPage):
-    """Root class for all wizard pages."""
+class BaseWizardPage(QWizardPage):
+    """Base class for all wizard pages."""
 
     ui_class = None
-    _signals = {}  # override in children
+    max_width = 0
     processingStarted = pyqtSignal()
     processingFinished = pyqtSignal()
 
-    def __init__(self, app_name, **kwargs):
-        """Create a new instance."""
-        parent = kwargs.pop('parent', None)
-        super(SSOWizardPage, self).__init__(parent=parent)
+    def __init__(self, parent=None):
+        super(BaseWizardPage, self).__init__(parent=parent)
 
         self.ui = None
         if self.ui_class is not None:
@@ -105,64 +104,39 @@
             self.ui = self.ui_class()
             self.ui.setupUi(self)
 
-        # store common useful data provided by the app
-        self.app_name = app_name
-        self.ping_url = kwargs.get('ping_url', '')
-        self.tc_url = kwargs.get('tc_url', '')
-        self.policy_url = kwargs.get('policy_url', '')
-        self.help_text = kwargs.get('help_text', '')
+        if self.layout() is None:
+            self.setLayout(QVBoxLayout(self))
 
         # Set the error area
-        self.form_errors_label = QLabel(' ')
+        self.form_errors_label = QLabel()
         self.form_errors_label.setObjectName('form_errors')
         self.form_errors_label.setAlignment(Qt.AlignBottom)
         self.layout().insertWidget(0, self.form_errors_label)
+
         # Set the header
-        self.header = Header()
+        self.header = WizardHeader(max_width=self.max_width)
         self.header.set_title(title='')
         self.header.set_subtitle(subtitle='')
         self.layout().insertWidget(0, self.header)
-        self._signals_receivers = {}
-        self.backend = None
 
         self.layout().setAlignment(Qt.AlignLeft)
 
-        self.setup_page()
-
-    def show_error(self, app_name, message):
-        """Show an error message inside the page."""
-        self.hide_overlay()
-        fm = QFontMetrics(self.form_errors_label.font())
-        width = PREFERED_UI_SIZE['width'] * 0.95
-        elided_text = fm.elidedText(message, Qt.ElideRight, width)
-        self.form_errors_label.setText(elided_text)
-        self.form_errors_label.setToolTip(message)
-
-    def hide_error(self):
-        """Hide the label errors in the current page."""
-        # We actually want the label with one chat, because if it is an
-        # empty string, the height of the label is 0
-        self.form_errors_label.setText(' ')
-
-    def hide_overlay(self):
-        """Emit the signal to notify the upper container that ends loading."""
-        self.setEnabled(True)
-        self.processingFinished.emit()
-
-    def show_overlay(self):
-        """Emit the signal to notify the upper container that is loading."""
-        self.setEnabled(False)
-        self.processingStarted.emit()
-
-    @defer.inlineCallbacks
-    def setup_page(self):
-        """Setup the widget components."""
-        client = yield main.get_sso_client()
-        self.backend = client.sso_login
-
-        self._setup_signals()
-        self._set_translated_strings()
-        self._connect_ui()
+        self._is_processing = False
+
+    def _get_is_processing(self):
+        """Is this widget processing any request?"""
+        return self._is_processing
+
+    def _set_is_processing(self, new_value):
+        """Set this widget to be processing a request."""
+        self._is_processing = new_value
+        self.setEnabled(not new_value)
+        if not self._is_processing:
+            self.processingFinished.emit()
+        else:
+            self.processingStarted.emit()
+
+    is_processing = property(fget=_get_is_processing, fset=_set_is_processing)
 
     # pylint: disable=C0103
 
@@ -172,7 +146,7 @@
 
     def setTitle(self, title=''):
         """Set the Wizard Page Title."""
-        self.header.set_title(TITLE_STYLE % title)
+        self.header.set_title(title)
 
     def setSubTitle(self, subtitle=''):
         """Set the Wizard Page Subtitle."""
@@ -188,6 +162,74 @@
 
     # pylint: enable=C0103
 
+    @log_call(logger.error)
+    def show_error(self, message):
+        """Show an error message inside the page."""
+        self.is_processing = False
+        maybe_elide_text(self.form_errors_label, message,
+                         self.max_width * 0.95, markup=ERROR_STYLE)
+
+    def hide_error(self):
+        """Hide the label errors in the current page."""
+        # We actually want the label with one empty char, because if it is an
+        # empty string, the height of the label is 0
+        self.form_errors_label.setText(' ')
+
+
+class SSOWizardPage(BaseWizardPage):
+    """Root class for all SSO specific wizard pages."""
+
+    _signals = {}  # override in children
+    max_width = PREFERED_UI_SIZE['width']
+
+    def __init__(self, app_name, **kwargs):
+        """Create a new instance."""
+        parent = kwargs.pop('parent', None)
+        super(SSOWizardPage, self).__init__(parent=parent)
+
+        # store common useful data provided by the app
+        self.app_name = app_name
+        self.ping_url = kwargs.get('ping_url', '')
+        self.tc_url = kwargs.get('tc_url', '')
+        self.policy_url = kwargs.get('policy_url', '')
+        self.help_text = kwargs.get('help_text', '')
+
+        self._signals_receivers = {}
+        self.backend = None
+
+        self.setup_page()
+
+    def hide_overlay(self):
+        """Emit the signal to notify the upper container that ends loading."""
+        self.is_processing = False
+
+    def show_overlay(self):
+        """Emit the signal to notify the upper container that is loading."""
+        self.is_processing = True
+
+    @defer.inlineCallbacks
+    def setup_page(self):
+        """Setup the widget components."""
+        logger.info('Starting setup_page for: %r', self)
+        # pylint: disable=W0702,W0703
+        try:
+            # Get Backend
+            client = yield main.get_sso_client()
+            self.backend = client.sso_login
+            self._set_translated_strings()
+            self._connect_ui()
+            # Call _setup_signals at the end, so we ensure that the UI
+            # is at least styled as expected if the operations with the
+            # backend fails.
+            self._setup_signals()
+        except:
+            message = 'There was a problem trying to setup the page %r' % self
+            self.show_error(message)
+            logger.exception(message)
+            self.setEnabled(False)
+        # pylint: enable=W0702,W0703
+        logger.info('%r - setup_page ends, backend is %r.', self, self.backend)
+
     def _filter_by_app_name(self, f):
         """Excecute the decorated function only for 'self.app_name'."""
 
@@ -218,11 +260,9 @@
 
     def _set_translated_strings(self):
         """Implement in each child."""
-        raise NotImplementedError()
 
     def _connect_ui(self):
         """Implement in each child."""
-        raise NotImplementedError()
 
     def _handle_error(self, remote_call, handler, error):
         """Handle any error when calling the remote backend."""

=== modified file 'ubuntu_sso/qt/tests/__init__.py'
--- ubuntu_sso/qt/tests/__init__.py	2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/tests/__init__.py	2012-03-20 16:10:09 +0000
@@ -21,7 +21,7 @@
 from twisted.trial.unittest import TestCase
 
 from ubuntu_sso import main, NO_OP
-from ubuntu_sso.qt import TITLE_STYLE
+from ubuntu_sso.qt import ERROR_STYLE, maybe_elide_text, TITLE_STYLE
 from ubuntu_sso.tests import (
     APP_NAME,
     HELP_TEXT,
@@ -38,6 +38,15 @@
 # pylint: disable=W0212
 
 
+def build_string_for_pixels(label, width):
+    """Return a random string that will be as big as with in pixels."""
+    char = 'a'
+    fm = QtGui.QFontMetrics(label.font())
+    pixel_width = fm.width(char)
+    chars = int(width / pixel_width)
+    return char * chars
+
+
 class FakedObject(object):
     """Fake an object, record every call."""
 
@@ -439,6 +448,9 @@
                 self.wizard = self.ui_wizard_class()
                 self.patch(self.ui, 'wizard', lambda: self.wizard)
 
+            self.ui.show()
+            self.addCleanup(self.ui.hide)
+
     def _set_called(self, *args, **kwargs):
         """Store 'args' and 'kwargs' for test assertions."""
         self._called = (args, kwargs)
@@ -467,6 +479,38 @@
 
         self.assertEqual(self.signal_results, [signal_args])
 
+    def assert_title_correct(self, title_label, expected, max_width):
+        """Check that the label's text is equal to 'expected'."""
+        label = QtGui.QLabel()
+        maybe_elide_text(label, expected, max_width)
+
+        self.assertEqual(TITLE_STYLE % unicode(label.text()),
+                         unicode(title_label.text()))
+        self.assertEqual(unicode(label.toolTip()),
+                         unicode(title_label.toolTip()))
+        self.assertTrue(title_label.isVisible())
+
+    def assert_subtitle_correct(self, subtitle_label, expected, max_width):
+        """Check that the subtitle is equal to 'expected'."""
+        label = QtGui.QLabel()
+        maybe_elide_text(label, expected, max_width)
+
+        self.assertEqual(unicode(label.text()), unicode(subtitle_label.text()))
+        self.assertEqual(unicode(label.toolTip()),
+                         unicode(subtitle_label.toolTip()))
+        self.assertTrue(subtitle_label.isVisible())
+
+    def assert_error_correct(self, error_label, expected, max_width):
+        """Check that the error 'error_label' displays 'expected' as text."""
+        label = QtGui.QLabel()
+        maybe_elide_text(label, expected, max_width)
+
+        self.assertEqual(ERROR_STYLE % unicode(label.text()),
+                         unicode(error_label.text()))
+        self.assertEqual(unicode(label.toolTip()),
+                         unicode(error_label.toolTip()))
+        self.assertTrue(error_label.isVisible())
+
     def get_pixmap_data(self, pixmap):
         """Get the raw data of a QPixmap."""
         byte_array = QtCore.QByteArray()
@@ -507,7 +551,7 @@
             self.assertIn(signal, self.ui._signals)
             self.assertTrue(callable(self.ui._signals[signal]))
 
-        expected = ['_setup_signals', '_set_translated_strings', '_connect_ui']
+        expected = ['_set_translated_strings', '_connect_ui', '_setup_signals']
         self.assertEqual(expected, called)
 
 
@@ -550,14 +594,18 @@
         self.assertEqual(self._overlay_hide_counter, 1)
         self.assertTrue(self.ui.isEnabled())
 
+    # pylint: disable=W0221
+
     def assert_title_correct(self, expected):
         """Check that the title is equal to 'expected'."""
-        self.assertEqual(TITLE_STYLE % expected, unicode(self.ui.title()))
+        check = super(PageBaseTestCase, self).assert_title_correct
+        check(self.ui.header.title_label, expected,
+              self.ui.header.max_title_width)
 
     def assert_subtitle_correct(self, expected):
         """Check that the subtitle is equal to 'expected'."""
-        elided_text = unicode(self.ui.subTitle())
-        elided_text = elided_text[:len(elided_text) - 1]
+        check = super(PageBaseTestCase, self).assert_subtitle_correct
+        check(self.ui.header.subtitle_label, expected,
+              self.ui.header.max_subtitle_width)
 
-        self.assertTrue(expected.startswith(elided_text))
-        self.assertEqual(self.ui.header.subtitle_label.toolTip(), expected)
+    # pylint: enable=W0221

=== modified file 'ubuntu_sso/qt/tests/login_u_p.py'
--- ubuntu_sso/qt/tests/login_u_p.py	2012-01-26 15:34:16 +0000
+++ ubuntu_sso/qt/tests/login_u_p.py	2012-03-20 16:10:09 +0000
@@ -39,7 +39,6 @@
 
     def found(*args):
         """The result was received."""
-        print "result received", args
         d.callback(args)
 
     client.cred_manager.connect_to_signal('CredentialsFound', found)
@@ -61,7 +60,6 @@
     yield cleared
 
     yield client.cred_manager.login_email_password('SUPER', args)
-    print "called ok"
     yield d
 
     yield client.disconnect()

=== modified file 'ubuntu_sso/qt/tests/show_gui.py'
--- ubuntu_sso/qt/tests/show_gui.py	2012-02-10 17:18:22 +0000
+++ ubuntu_sso/qt/tests/show_gui.py	2012-03-20 16:10:09 +0000
@@ -46,7 +46,6 @@
 
     def found(*args):
         """The result was received."""
-        print "result received", args
         d.callback(args)
 
     client.cred_manager.connect_to_signal('CredentialsFound', found)
@@ -62,7 +61,6 @@
             TC_URL_KEY: u'http://www.google.com/',
             UI_EXECUTABLE_KEY: 'ubuntu-sso-login-gtk',
         })
-    print "called ok"
     yield d
 
     yield client.disconnect()

=== modified file 'ubuntu_sso/qt/tests/test_common.py'
--- ubuntu_sso/qt/tests/test_common.py	2011-10-28 10:41:18 +0000
+++ ubuntu_sso/qt/tests/test_common.py	2012-03-20 16:10:09 +0000
@@ -20,7 +20,13 @@
 from twisted.internet import defer
 from twisted.trial.unittest import TestCase
 
-from ubuntu_sso.qt.common import (check_as_invalid,
+from ubuntu_sso.qt import (
+    build_general_error_message,
+    maybe_elide_text,
+    GENERIC_BACKEND_ERROR,
+)
+from ubuntu_sso.qt.common import (
+    check_as_invalid,
     check_as_valid,
     password_assistance,
     password_check_match,
@@ -30,7 +36,9 @@
     PASSWORD_DIGIT,
     PASSWORD_LENGTH,
     PASSWORD_MATCH,
-    PASSWORD_UPPER)
+    PASSWORD_UPPER,
+)
+from ubuntu_sso.qt.tests import build_string_for_pixels
 
 
 class PasswordTestCase(TestCase):
@@ -234,3 +242,123 @@
         line_edit = QtGui.QLineEdit()
         check_as_invalid(line_edit)
         self.assertTrue(line_edit.property("formError").toBool())
+
+
+class ElidedTextTestCase(TestCase):
+    """The test case for the maybe_elide_text function."""
+
+    max_width = 100
+
+    @defer.inlineCallbacks
+    def setUp(self):
+        """Setup tests."""
+        yield super(ElidedTextTestCase, self).setUp()
+        self.ui = QtGui.QLabel()
+
+    def test_text_not_elided_if_too_short(self):
+        """If text is shorter than max_width, do not elide."""
+        text = build_string_for_pixels(self.ui, self.max_width - 1)
+
+        maybe_elide_text(self.ui, text, self.max_width)
+
+        self.assertEqual(self.ui.toolTip(), '')
+        self.assertEqual(self.ui.text(), text)
+        self.assertNotIn(u'\u2026', self.ui.text())
+
+    def test_text_not_elided_if_equals_max_width(self):
+        """If text is equal than max_width, do not elide."""
+        text = build_string_for_pixels(self.ui, self.max_width)
+
+        maybe_elide_text(self.ui, text, self.max_width)
+
+        self.assertEqual(self.ui.toolTip(), '')
+        self.assertEqual(self.ui.text(), text)
+        self.assertNotIn(u'\u2026', self.ui.text())
+
+    def test_text_elided_if_bigger_than_max_width(self):
+        """If text is equal than max_width, do not elide."""
+        text = build_string_for_pixels(self.ui, self.max_width + 10)
+
+        maybe_elide_text(self.ui, text, self.max_width)
+
+        self.assertEqual(self.ui.toolTip(), text)
+        expected = unicode(self.ui.text())
+        self.assertTrue(expected.endswith(u'\u2026'))
+        self.assertTrue(text.startswith(expected[:-1]))
+
+
+class BuildGeneralErrorMessageTestCase(TestCase):
+    """Test passwords conditions."""
+
+    def test_with_message(self):
+        """Test build_general_error_message with 'message' key."""
+        error = "error message"
+        err_dict = {'message': error}
+
+        result = build_general_error_message(err_dict)
+
+        self.assertEqual(result, error)
+
+    def test_with_all(self):
+        """Test build_general_error_message with 'all' key."""
+        error = "error message"
+        err_dict = {'__all__': error}
+
+        result = build_general_error_message(err_dict)
+
+        self.assertEqual(result, error)
+
+    def test_with_message_and_all(self):
+        """Test build_general_error_message with 'all' and 'message' key."""
+        error = "error message"
+        error2 = "error message2"
+        err_dict = {'__all__': error, 'message': error2}
+
+        result = build_general_error_message(err_dict)
+
+        expected = '\n'.join((error, error2))
+        self.assertEqual(result, expected)
+
+    def test_with_all_and_error_message(self):
+        """Test for 'all' and 'error_message' key."""
+        error = "error message"
+        error2 = "error message2"
+        err_dict = {'__all__': error, 'error_message': error2}
+        result = build_general_error_message(err_dict)
+        expected = '\n'.join((error, error2))
+        self.assertEqual(result, expected)
+
+    def test_with_random_keys(self):
+        """Test build_general_error_message with random keys."""
+        error = "error message"
+        error2 = "error message2"
+        err_dict = {'my_bad': error, 'odd_error': error2}
+
+        result = build_general_error_message(err_dict)
+
+        expected = '\n'.join(
+            [('%s: %s' % (k, v)) for k, v in err_dict.iteritems()])
+        self.assertEqual(result, expected)
+
+    def test_with_random_keys_with_errtype(self):
+        """Test build_general_error_message with random keys and errtype."""
+        error = "error message"
+        error2 = "error message2"
+        err_dict = {'my_bad': error, 'odd_error': error2, 'errtype': 'Danger'}
+
+        result = build_general_error_message(err_dict)
+
+        expected = '\n'.join(
+            [('%s: %s' % (k, v)) \
+            for k, v in {'my_bad': error, 'odd_error': error2}.iteritems()])
+        self.assertEqual(result, expected)
+
+    def test_with_not_dict(self):
+        """Test build_general_error_message with argument not dict."""
+        error = "error message"
+        err_dict = Exception(error)
+
+        result = build_general_error_message(err_dict)
+
+        expected = GENERIC_BACKEND_ERROR
+        self.assertEqual(result, expected)

=== modified file 'ubuntu_sso/qt/tests/test_current_user_sign_in_page.py'
--- ubuntu_sso/qt/tests/test_current_user_sign_in_page.py	2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/tests/test_current_user_sign_in_page.py	2012-03-20 16:10:09 +0000
@@ -24,6 +24,7 @@
     FakePageUiStyle,
     FakeWizardButtonStyle,
 )
+from ubuntu_sso.tests import EMAIL
 
 
 # pylint: disable=W0212
@@ -51,19 +52,32 @@
         self.assertTrue(button.properties['default'])
         self.assertFalse(button.isEnabled())
 
+    def test_unicode_in_forgotten_password_link(self):
+        """Ensure that this label supports unicode."""
+        forgot_fr = u"J'ai oublié mon mot de passe"
+        self.patch(gui, "FORGOTTEN_PASSWORD_BUTTON", forgot_fr)
+        forgotten_text = gui.LINK_STYLE.format(link_url='#',
+                            link_text=forgot_fr)
+        self.ui._set_translated_strings()
+        self.assertEqual(unicode(self.ui.ui.forgot_password_label.text()),
+            forgotten_text)
+
     def test_set_translated_strings(self):
         """Test the translated string method."""
         expected = gui.LOGIN_TITLE.format(app_name=self.app_name)
         self.assert_title_correct(expected)
         expected = gui.LOGIN_SUBTITLE % dict(app_name=self.app_name)
         self.assert_subtitle_correct(expected)
-        self.assertEqual(self.ui.ui.email_label.text(), gui.EMAIL_LABEL)
-        self.assertEqual(self.ui.ui.password_label.text(),
+        self.assertEqual(unicode(self.ui.ui.email_label.text()),
+            gui.EMAIL_LABEL)
+        self.assertEqual(unicode(self.ui.ui.password_label.text()),
             gui.LOGIN_PASSWORD_LABEL)
         text = gui.LINK_STYLE.format(link_url='#',
                                      link_text=gui.FORGOTTEN_PASSWORD_BUTTON)
-        self.assertEqual(self.ui.ui.forgot_password_label.text(), text)
-        self.assertEqual(self.ui.ui.sign_in_button.text(), gui.SIGN_IN_BUTTON)
+        self.assertEqual(unicode(self.ui.ui.forgot_password_label.text()),
+            text)
+        self.assertEqual(unicode(self.ui.ui.sign_in_button.text()),
+            gui.SIGN_IN_BUTTON)
 
     def test_connect_ui(self):
         """Test the connect ui method."""
@@ -128,30 +142,27 @@
     def test_on_login_error(self):
         """Test the on_login_error method."""
         self.patch(self.ui, "show_error", self._set_called)
-        app_name = 'my_app'
-        self.ui.app_name = app_name
         error = {'errtype': 'UserNotValidated'}
-        self.ui.on_login_error(app_name, error)
+
+        self.ui.on_login_error(self.app_name, error)
+
         self.assertEqual(self._overlay_hide_counter, 0)
         self.assertTrue(self.ui.isEnabled())
-        expected = ((self.ui, 'my_app', ''), {})
+        expected = ((self.ui, self.app_name, ''), {})
         self.assertTrue(expected, self._called)
 
     def test_on_logged_in(self):
         """Test the on_login_in method."""
-        email = 'email@example'
-        self.ui.ui.email_edit.setText(email)
-
-        self.assert_signal_emitted(self.ui.userLoggedIn, (email,),
-            self.ui.on_logged_in, self.app_name, email)
+        self.ui.ui.email_edit.setText(EMAIL)
+        self.assert_signal_emitted(self.ui.userLoggedIn, (EMAIL,),
+            self.ui.on_logged_in, self.app_name, EMAIL)
         self.assertTrue(self.ui.isEnabled())
 
     def test_on_user_not_validated(self):
         """Test the navigation flow on user not validated."""
-        email = 'email@example'
-        self.ui.ui.email_edit.setText(email)
-        self.assert_signal_emitted(self.ui.userNotValidated, (email,),
-            self.ui.on_user_not_validated, self.app_name, email)
+        self.ui.ui.email_edit.setText(EMAIL)
+        self.assert_signal_emitted(self.ui.userNotValidated, (EMAIL,),
+            self.ui.on_user_not_validated, self.app_name, EMAIL)
 
     def test_on_forgotten_password(self):
         """Test the on_forgotten_password method."""

=== modified file 'ubuntu_sso/qt/tests/test_email_verification.py'
--- ubuntu_sso/qt/tests/test_email_verification.py	2012-03-05 18:56:50 +0000
+++ ubuntu_sso/qt/tests/test_email_verification.py	2012-03-20 16:10:09 +0000
@@ -78,14 +78,12 @@
         email = 'mail@example'
         self.ui.email = email
         self.ui.set_titles(email)
-        self.assertEqual(self.ui.header.title_label.text(),
-            email_verification_page.VERIFY_EMAIL_TITLE)
+        self.assert_title_correct(email_verification_page.VERIFY_EMAIL_TITLE)
         expected = email_verification_page.VERIFY_EMAIL_CONTENT % {
                        "app_name": self.app_name,
                        "email": email,
                    }
-        self.assertEqual(unicode(self.ui.header.subtitle_label.toolTip()),
-            expected)
+        self.assert_subtitle_correct(expected)
 
     def test_initialize_page(self):
         """Test the initialization method."""
@@ -100,10 +98,11 @@
     def test_on_email_validation_error(self):
         """Test the validate_email method."""
         self.patch(self.ui, "show_error", self._set_called)
-        app_name = 'my_app'
         error = {email_verification_page: 'error in email_verification_page'}
-        self.ui.on_email_validation_error(app_name, error)
-        expected = ((self.ui, app_name, ''), {})
+
+        self.ui.on_email_validation_error(self.app_name, error)
+
+        expected = ((self.ui, ''), {})
         self.assertTrue(expected, self._called)
         self.assertEqual(self._overlay_hide_counter, 1)
 

=== modified file 'ubuntu_sso/qt/tests/test_enhanced_check_box.py'
--- ubuntu_sso/qt/tests/test_enhanced_check_box.py	2012-02-16 18:40:41 +0000
+++ ubuntu_sso/qt/tests/test_enhanced_check_box.py	2012-03-20 16:10:09 +0000
@@ -16,7 +16,7 @@
 
 """Tests for the EnhancedCheckBox widget."""
 
-from PyQt4 import QtCore
+from PyQt4 import QtGui, QtCore
 
 from ubuntu_sso.qt import enhanced_check_box
 from ubuntu_sso.qt.tests import BaseTestCase
@@ -40,3 +40,21 @@
         check.setText("text")
         self.assertEqual(check.text(), "text")
         self.assertEqual(check.text(), check.text_label.text())
+
+    def test_enhanced_check_size_adjust_with_small_height(self):
+        """Check if the size of the EnhancedCheckBox is adjusted correctly."""
+        text = 't' * 200
+        height = 10
+        widget = QtGui.QWidget()
+        widget.setFixedSize(200, height)
+        check = enhanced_check_box.EnhancedCheckBox(text, widget)
+        self.assertTrue(check.height() > height)
+
+    def test_enhanced_check_size_adjust_with_big_height(self):
+        """Check if the size of the EnhancedCheckBox is adjusted correctly."""
+        text = 't' * 20
+        height = 200
+        widget = QtGui.QWidget()
+        widget.setFixedSize(200, height)
+        check = enhanced_check_box.EnhancedCheckBox(text, widget)
+        self.assertTrue(check.height() < height)

=== modified file 'ubuntu_sso/qt/tests/test_forgotten_password.py'
--- ubuntu_sso/qt/tests/test_forgotten_password.py	2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/tests/test_forgotten_password.py	2012-03-20 16:10:09 +0000
@@ -75,9 +75,10 @@
         subtitle = gui.FORGOTTEN_PASSWORD_SUBTITLE
         self.assert_subtitle_correct(subtitle.format(app_name=self.app_name))
 
-        self.assertEqual(self.ui.ui.email_address_label.text(),
+        self.assertEqual(unicode(self.ui.ui.email_address_label.text()),
             gui.EMAIL_LABEL)
-        self.assertEqual(self.ui.ui.send_button.text(), gui.RESET_PASSWORD)
+        self.assertEqual(unicode(self.ui.ui.send_button.text()),
+            gui.RESET_PASSWORD)
 
     def test_connect_ui(self):
         """Test the connect ui method."""
@@ -112,7 +113,8 @@
         """Test on_password_reset_error method."""
         self.patch(self.ui, "show_error", self._set_called)
         error = {'errtype': 'FooBarBaz'}
+
         self.ui.on_password_reset_error(self.app_name, error)
-        expected = ((self.ui, self.app_name,
-            gui.REQUEST_PASSWORD_TOKEN_WRONG_EMAIL), {})
+
+        expected = ((self.ui, gui.REQUEST_PASSWORD_TOKEN_WRONG_EMAIL), {})
         self.assertTrue(expected, self._called)

=== modified file 'ubuntu_sso/qt/tests/test_loadingoverlay.py'
--- ubuntu_sso/qt/tests/test_loadingoverlay.py	2012-02-23 19:49:02 +0000
+++ ubuntu_sso/qt/tests/test_loadingoverlay.py	2012-03-20 16:10:09 +0000
@@ -29,8 +29,5 @@
 
     def test_status_correct(self):
         """Test if the necessary variables for the animation exists"""
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
-
         self.assertTrue(self.ui.counter is not None)
         self.assertTrue(self.ui.orientation is not None)

=== modified file 'ubuntu_sso/qt/tests/test_main.py'
--- ubuntu_sso/qt/tests/test_main.py	2012-02-13 15:43:59 +0000
+++ ubuntu_sso/qt/tests/test_main.py	2012-03-20 16:10:09 +0000
@@ -16,27 +16,123 @@
 
 """Tests for the main module."""
 
+from PyQt4 import QtCore
+from twisted.internet import defer
 from twisted.trial.unittest import TestCase
 
 from ubuntu_sso.qt import main
+from ubuntu_sso import tests
+
+
+# pylint: disable=C0103
+class FakeUi(object):
+
+    """A fake UI."""
+
+    def size(self):
+        """Fake size."""
+        return QtCore.QSize(100, 100)
+
+    def setGeometry(self, *args):
+        """Fake setGeometry."""
+
+    show = setGeometry
+
+
+class FakeDesktop(object):
+
+    """Fake Desktop Widget."""
+
+    def availableGeometry(self):
+        """Fake availableGeometry for desktop.-"""
+        return QtCore.QRect(100, 100, 100, 100)
+
+
+class FakeApplication(object):
+
+    """Fake QApplication."""
+
+    called = {}
+    __instance = None
+
+    def __init__(self, args):
+        self.called['args'] = args
+        FakeApplication.__instance = self
+
+    def setStyleSheet(self, style):
+        """Fake setStyleSheet."""
+        self.called["setStyleSheet"] = style
+
+    def styleSheet(self):
+        """Fake get style sheet."""
+        return self.called.get("setStyleSheet", '')
+
+    def desktop(self):
+        """Fake Desktop."""
+        return FakeDesktop()
+
+    def exec_(self):
+        """Fake exec_."""
+
+    def exit(self):
+        """Fake exit."""
+
+    @classmethod
+    def instance(cls):
+        """Fake instance."""
+        return FakeApplication.__instance
+# pylint: enable=C0103
 
 
 class BasicTestCase(TestCase):
     """Test case with a helper tracker."""
 
+    @defer.inlineCallbacks
+    def setUp(self):
+        yield super(BasicTestCase, self).setUp()
+        self.called = []
+
+        def called_ui(**kw):
+            """record ui call."""
+            self.called.append(('GUI', kw))
+            return FakeUi()
+
+        self.patch(main, 'UbuntuSSOClientGUI', called_ui)
+        self.patch(main.QtGui, 'QApplication', FakeApplication)
+
     def test_main(self):
         """Calling main.main() a UI instance is created."""
-        called = []
-        self.patch(main, 'UbuntuSSOClientGUI',
-                   lambda **kw: called.append(('GUI', kw)))
-        self.patch(main.QtGui.QApplication, 'exec_',
-                   lambda *a: called.append('main'))
-
+        kwargs = dict(app_name='APP_NAME', foo='foo', bar='bar',
+            baz='yadda', yadda=0)
+        main.main(**kwargs)
+
+        kwargs['close_callback'] = main.QtGui.QApplication.instance().exit
+        self.assertEqual(self.called, [('GUI', kwargs)])
+
+    def test_main_args(self):
+        """Calling main.main() a UI instance is created."""
+        arg_list = (tests.APP_NAME, tests.NAME, tests.PASSWORD,
+            tests.EMAIL_TOKEN)
+        kwargs = dict(app_name=arg_list[0].encode('utf-8'),
+            foo=arg_list[1].encode('utf-8'),
+            bar=arg_list[2].encode('utf-8'),
+            baz=arg_list[3].encode('utf-8'))
+        main.main(**kwargs)
+
+        kwargs['close_callback'] = main.QtGui.QApplication.instance().exit
+        expected = dict(app_name=arg_list[0], foo=arg_list[1],
+            bar=arg_list[2], baz=arg_list[3],
+            close_callback=main.QtGui.QApplication.instance().exit)
+        self.assertEqual(self.called, [('GUI', expected)])
+
+    def test_styles_load(self):
+        """Test that all stylesheets load."""
         kwargs = dict(foo='foo', bar='bar', baz='yadda', yadda=0)
         main.main(**kwargs)
-
-        kwargs['close_callback'] = main.QtGui.QApplication.instance().exit
-        self.assertEqual(called, [('GUI', kwargs), 'main'])
-
-    test_main.skip = 'Failing with QObject::startTimer: QTimer can only be ' \
-                     'used with threads started with QThread'
+        data = []
+        for qss_name in (main.PLATFORM_QSS, ":/stylesheet.qss"):
+            qss = QtCore.QResource(qss_name)
+            data.append(unicode(qss.data()))
+        self.assertEqual(
+        unicode(main.QtGui.QApplication.instance().styleSheet()),
+            '\n'.join(data))

=== modified file 'ubuntu_sso/qt/tests/test_proxy_dialog.py'
--- ubuntu_sso/qt/tests/test_proxy_dialog.py	2012-02-13 16:16:03 +0000
+++ ubuntu_sso/qt/tests/test_proxy_dialog.py	2012-03-20 16:10:09 +0000
@@ -256,7 +256,7 @@
         self.patch(dialog, 'done', fake_done)
 
         dialog._on_cancel_clicked()
-        self.assertIn(('done', proxy_dialog.USER_CANCELATION), called)
+        self.assertIn(('done', proxy_dialog.USER_CANCELLATION), called)
 
     def assert_save_button(self, set_creds_callback, result_number):
         """Test the save button execution."""
@@ -288,7 +288,7 @@
     def test_on_save_clicked_correct(self):
         """Test that we do save the creds."""
         set_creds_cb = lambda: defer.succeed(True)
-        result_number = proxy_dialog.CREDS_ACQUIRED
+        result_number = proxy_dialog.USER_SUCCESS
         self.assert_save_button(set_creds_cb, result_number)
 
 

=== modified file 'ubuntu_sso/qt/tests/test_reset_password.py'
--- ubuntu_sso/qt/tests/test_reset_password.py	2012-03-05 18:56:50 +0000
+++ ubuntu_sso/qt/tests/test_reset_password.py	2012-03-20 16:10:09 +0000
@@ -70,27 +70,23 @@
 
     def test_initialize(self):
         """Check the Title and Subtitle."""
-        self.ui.show()
         self.ui.initializePage()
-        self.addCleanup(self.ui.hide)
         self.assert_title_correct(RESET_TITLE)
         self.assert_subtitle_correct(RESET_SUBTITLE)
-        self.assertEqual(self.ui.ui.password_label.text(), PASSWORD1_ENTRY)
-        self.assertEqual(self.ui.ui.confirm_password_label.text(),
+        self.assertEqual(unicode(self.ui.ui.password_label.text()),
+            PASSWORD1_ENTRY)
+        self.assertEqual(unicode(self.ui.ui.confirm_password_label.text()),
             PASSWORD2_ENTRY)
-        self.assertEqual(self.ui.ui.reset_code.text(), RESET_CODE_ENTRY)
+        self.assertEqual(unicode(self.ui.ui.reset_code.text()),
+            RESET_CODE_ENTRY)
 
     def test_focus_changed_password_visibility(self):
         """Check visibility changes when focus_changed() is executed."""
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.ui.focus_changed(None, self.ui.ui.password_line_edit)
         self.assertTrue(self.ui.ui.password_assistance.isVisible())
 
     def test_show_hide_event(self):
         """Check connections to focusChanged on show and hide event."""
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.assertEqual(QtGui.QApplication.instance().receivers(
             QtCore.SIGNAL("focusChanged(QWidget*, QWidget*)")), 1)
         self.ui.hide()
@@ -102,24 +98,20 @@
     def test_focus_changed_1(self):
         """Check functions execution when focus_changed() is executed."""
         self.patch(common, 'password_default_assistance', self._set_called)
-
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
-
         self.assertFalse(self._called)
+
         self.ui.focus_changed(None, self.ui.ui.password_line_edit)
+
         self.assertTrue(self.ui.ui.password_assistance.isVisible())
         self.assertTrue(self._called)
 
     def test_focus_changed_2(self):
         """Check functions execution when focus_changed() is executed."""
         self.patch(common, 'password_check_match', self._set_called)
-
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
-
         self.assertFalse(self._called)
+
         self.ui.focus_changed(None, self.ui.ui.confirm_password_line_edit)
+
         self.assertTrue(self.ui.ui.password_assistance.isVisible())
         self.assertTrue(self._called)
 

=== modified file 'ubuntu_sso/qt/tests/test_setup_account.py'
--- ubuntu_sso/qt/tests/test_setup_account.py	2012-03-06 14:13:10 +0000
+++ ubuntu_sso/qt/tests/test_setup_account.py	2012-03-20 16:10:09 +0000
@@ -43,13 +43,20 @@
         """
         self.ui.ui.name_edit.setText("")
         self.ui.name_assistance()
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.assertTrue(self.ui.ui.name_assistance.isVisible())
-        self.assertEqual(
-            unicode(self.ui.ui.name_assistance.text()),
-            gui.ERROR_STYLE % gui.EMPTY_NAME)
-        self.ui.hide()
+        self.assert_error_correct(self.ui.ui.name_assistance, gui.EMPTY_NAME,
+                                  max_width=self.ui.header.max_title_width)
+
+    def test_hide_error_on_refresh_clicked(self):
+        """Hide form errors when the user click to refresh the captcha."""
+        self.ui.show_error('error')
+        self.assert_error_correct(self.ui.form_errors_label, 'error',
+            max_width=self.ui.header.max_title_width)
+
+        self.ui.ui.refresh_label.linkActivated.emit('error')
+
+        message = unicode(self.ui.form_errors_label.text())
+        self.assertEqual(message, ' ')
 
     def test_enable_setup_button_with_visible_check(self):
         """Test _enable_setup_button method with terms check visible."""
@@ -65,8 +72,6 @@
         self.ui.ui.captcha_solution_edit.setText('captcha solution')
         self.ui.terms_checkbox.setChecked(True)
 
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.ui.terms_checkbox.setVisible(True)
         self.ui.ui.captcha_solution_edit.textEdited.emit('')
         self.assertTrue(self.ui.set_up_button.isEnabled())
@@ -84,8 +89,6 @@
         self.ui.ui.confirm_password_edit.setText(password)
         self.ui.ui.captcha_solution_edit.setText('captcha solution')
 
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.ui.terms_checkbox.setVisible(False)
         self.ui.ui.captcha_solution_edit.textEdited.emit('')
         self.assertTrue(self.ui.set_up_button.isEnabled())
@@ -114,8 +117,6 @@
 
     def test_password_focus_gain(self):
         """Check functions execution when focus_changed() is executed."""
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.ui.ui.password_assistance.setVisible(False)
         self.assertFalse(self.ui.ui.password_assistance.isVisible())
         self.patch(self.ui, 'name_assistance', self._set_called)
@@ -174,7 +175,6 @@
         """Test on_user_registered method."""
         email = 'email@example'
         self.ui.ui.email_edit.setText(email)
-
         self.assert_signal_emitted(self.ui.userRegistered, (email,),
             self.ui.on_user_registered, self.app_name, email)
 
@@ -193,8 +193,6 @@
     def test_initialize_page(self):
         """Widgets are properly initialized."""
         self.ui.initializePage()
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
 
         # set up account button
         expected = [QtGui.QWizard.BackButton, QtGui.QWizard.Stretch,
@@ -210,12 +208,15 @@
         self.assertFalse(self.ui.captcha_received)
 
         # labels
-        self.assertEqual(self.ui.ui.name_label.text(), gui.NAME_ENTRY)
-        self.assertEqual(self.ui.ui.email_label.text(), gui.EMAIL)
-        self.assertEqual(self.ui.ui.confirm_email_label.text(),
+        self.assertEqual(unicode(self.ui.ui.name_label.text()),
+            gui.NAME_ENTRY)
+        self.assertEqual(unicode(self.ui.ui.email_label.text()),
+            gui.EMAIL)
+        self.assertEqual(unicode(self.ui.ui.confirm_email_label.text()),
                          gui.RETYPE_EMAIL)
-        self.assertEqual(self.ui.ui.password_label.text(), gui.PASSWORD)
-        self.assertEqual(self.ui.ui.confirm_password_label.text(),
+        self.assertEqual(unicode(self.ui.ui.password_label.text()),
+            gui.PASSWORD)
+        self.assertEqual(unicode(self.ui.ui.confirm_password_label.text()),
                          gui.RETYPE_PASSWORD)
 
         # assistants
@@ -230,8 +231,6 @@
         self.patch(self.ui, 'set_next_validation', self._set_called)
         self.ui.initializePage()
         self.ui.captcha_received = True
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
 
         self.ui.set_up_button.clicked.emit(False)
         self.assertEqual(self._called, ((False,), {}))
@@ -239,13 +238,10 @@
     def test_set_error_message(self):
         """Check the state of the label after calling: set_error_message."""
         self.ui.email_assistance()
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.ui.set_error_message(self.ui.ui.email_assistance, "message")
         self.assertTrue(self.ui.ui.email_assistance.isVisible())
-        self.assertEqual(
-            unicode(self.ui.ui.email_assistance.text()),
-            gui.ERROR_STYLE % "message")
+        self.assert_error_correct(self.ui.ui.email_assistance, "message",
+                                  max_width=self.ui.header.max_title_width)
 
     def test_blank_name(self):
         """Status when the name field is blank (spaces).
@@ -255,13 +251,9 @@
         """
         self.ui.ui.name_edit.setText("  ")
         self.ui.name_assistance()
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.assertTrue(self.ui.ui.name_assistance.isVisible())
-        self.assertEqual(
-            unicode(self.ui.ui.name_assistance.text()),
-            gui.ERROR_STYLE % gui.EMPTY_NAME)
-        self.ui.hide()
+        self.assert_error_correct(self.ui.ui.name_assistance, gui.EMPTY_NAME,
+                                  max_width=self.ui.header.max_title_width)
 
     def test_valid_name(self):
         """Status when the name field is valid.
@@ -270,10 +262,7 @@
         """
         self.ui.ui.name_edit.setText("John Doe")
         self.ui.name_assistance()
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.assertFalse(self.ui.ui.name_assistance.isVisible())
-        self.ui.hide()
 
     def test_invalid_email(self):
         """Status when the email field has no @.
@@ -283,12 +272,10 @@
         """
         self.ui.ui.email_edit.setText("foobar")
         self.ui.email_assistance()
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.assertTrue(self.ui.ui.email_assistance.isVisible())
-        self.assertEqual(
-            unicode(self.ui.ui.email_assistance.text()),
-            gui.ERROR_STYLE % gui.INVALID_EMAIL)
+        self.assert_error_correct(self.ui.ui.email_assistance,
+                                  gui.INVALID_EMAIL,
+                                  max_width=self.ui.header.max_title_width)
 
     def test_valid_email(self):
         """Status when the email field has a @.
@@ -297,10 +284,7 @@
         """
         self.ui.ui.email_edit.setText("foo@bar")
         self.ui.email_assistance()
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.assertFalse(self.ui.ui.email_assistance.isVisible())
-        self.ui.hide()
 
     def test_matching_emails(self):
         """Status when the email fields match.
@@ -310,10 +294,7 @@
         self.ui.ui.email_edit.setText("foo@bar")
         self.ui.ui.confirm_email_edit.setText("foo@bar")
         self.ui.confirm_email_assistance()
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.assertFalse(self.ui.ui.confirm_email_assistance.isVisible())
-        self.ui.hide()
 
     def test_not_matching_emails(self):
         """Status when the email fields don't match.
@@ -324,18 +305,13 @@
         self.ui.ui.email_edit.setText("foo@bar")
         self.ui.ui.confirm_email_edit.setText("foo@baz")
         self.ui.confirm_email_assistance()
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.assertTrue(self.ui.ui.confirm_email_assistance.isVisible())
-        self.assertEqual(
-            unicode(self.ui.ui.confirm_email_assistance.text()),
-            gui.ERROR_STYLE % gui.EMAIL_MATCH)
-        self.ui.hide()
+        self.assert_error_correct(self.ui.ui.confirm_email_assistance,
+                                  gui.EMAIL_MATCH,
+                                  max_width=self.ui.header.max_title_width)
 
     def test_focus_changed_password_visibility(self):
         """Check visibility changes when focus_changed() is executed."""
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         self.ui.focus_changed(None, self.ui.ui.password_edit)
         self.assertTrue(self.ui.ui.password_assistance.isVisible())
 
@@ -350,33 +326,41 @@
         self.ui.showEvent(QtGui.QShowEvent())
         self.ui.hideEvent(QtGui.QHideEvent())
 
-    def test_on_captcha_refreshing(self):
+    def test_on_captcha_refreshing_visible(self):
         """Check the state of the overlay on captcha refreshing."""
-        self.assertEqual(self._overlay_show_counter, 0)
-        self.ui.on_captcha_refreshing()
-        self.assertEqual(self._overlay_show_counter, 0)
-        self.assertTrue(self.ui.isEnabled())
-        self.ui.captcha_received = True
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
-        self.assertEqual(self._overlay_show_counter, 0)
-        self.assertTrue(self.ui.isEnabled())
-        self.ui.on_captcha_refreshing()
+        self.ui.hide_overlay()
+
+        self.assertEqual(self._overlay_show_counter, 0)
+        self.assertTrue(self.ui.isEnabled())
+
+        self.ui.on_captcha_refreshing()
+
         self.assertFalse(self.ui.isEnabled())
         self.assertEqual(self._overlay_show_counter, 1)
 
+    def test_on_captcha_refreshing_not_visible(self):
+        """Check the state of the overlay on captcha refreshing."""
+        self.ui.hide_overlay()
+
+        self.assertEqual(self._overlay_show_counter, 0)
+        self.assertTrue(self.ui.isEnabled())
+
+        self.ui.hide()
+        self.ui.on_captcha_refreshing()
+
+        self.assertEqual(self._overlay_show_counter, 0)
+        self.assertTrue(self.ui.isEnabled())
+
     def test_on_captcha_refresh_complete(self):
         """Check the state of the overlay on captcha refreshing complete."""
         self.assertEqual(self._overlay_hide_counter, 0)
-        self.assertTrue(self.ui.isEnabled())
+
         self.ui.on_captcha_refresh_complete()
+
         self.assertEqual(self._overlay_hide_counter, 1)
-        self.assertTrue(self.ui.isEnabled())
 
     def test_hide_error_on_refresh_captcha(self):
         """Test that the errors are hidden on refresh captcha."""
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
-        self.ui.show_error(self.app_name, 'error-message')
+        self.ui.show_error('error-message')
         self.ui.ui.refresh_label.linkActivated.emit('link')
         self.assertEqual(self.ui.form_errors_label.text(), ' ')

=== modified file 'ubuntu_sso/qt/tests/test_ssl_dialog.py'
--- ubuntu_sso/qt/tests/test_ssl_dialog.py	2012-03-05 16:32:37 +0000
+++ ubuntu_sso/qt/tests/test_ssl_dialog.py	2012-03-20 16:10:09 +0000
@@ -142,7 +142,8 @@
 
     def test_set_expander(self):
         """Test that the expander is correctly set."""
-        self.assertEqual(SSL_CERT_DETAILS, self.dialog.expander.text())
+        self.assertEqual(SSL_CERT_DETAILS,
+            unicode(self.dialog.expander.text()))
         self.assertNotEqual(None, self.dialog.expander.content)
         self.assertEqual(2, self.dialog.ui.expander_layout.indexOf(
                                                         self.dialog.expander))

=== modified file 'ubuntu_sso/qt/tests/test_sso_wizard_page.py'
--- ubuntu_sso/qt/tests/test_sso_wizard_page.py	2012-03-05 18:56:50 +0000
+++ ubuntu_sso/qt/tests/test_sso_wizard_page.py	2012-03-20 16:10:09 +0000
@@ -16,98 +16,137 @@
 
 """Test the SSOWizardPage and related."""
 
-from twisted.internet import defer
-
-from ubuntu_sso.qt import PREFERED_UI_SIZE
-from ubuntu_sso.qt.setup_account_page import SetupAccountPage
-from ubuntu_sso.qt.sso_wizard_page import Header
-from ubuntu_sso.qt.tests import BaseTestCase, PageBaseTestCase
-
-
-class HeaderTest(BaseTestCase):
+from ubuntu_sso.qt import PREFERED_UI_SIZE, sso_wizard_page as gui
+from ubuntu_sso.qt.tests import (
+    APP_NAME,
+    BaseTestCase,
+    build_string_for_pixels,
+    PageBaseTestCase,
+)
+
+
+MAX_WIDTH = 100
+
+
+class WizardHeaderTestCase(BaseTestCase):
 
     """Tests for injected Header in each Wizard Page."""
 
-    @defer.inlineCallbacks
-    def setUp(self):
-        yield super(HeaderTest, self).setUp()
-        self.header = Header()
+    kwargs = dict(max_width=MAX_WIDTH)
+    ui_class = gui.WizardHeader
+    ui_wizard_class = None
 
     def test_label_state(self):
         """Check the title and subtitle properties."""
-        self.assertTrue(self.header.title_label.wordWrap())
-        self.assertTrue(self.header.subtitle_label.wordWrap())
-        self.assertFalse(self.header.title_label.isVisible())
-        self.assertFalse(self.header.subtitle_label.isVisible())
+        self.assertTrue(self.ui.title_label.wordWrap())
+        self.assertTrue(self.ui.subtitle_label.wordWrap())
+        self.assertFalse(self.ui.title_label.isVisible())
+        self.assertFalse(self.ui.subtitle_label.isVisible())
 
     def test_set_title(self):
         """Check if set_title works ok, showing the widget if necessary."""
-        self.header.set_title('title')
-        self.assertEqual(self.header.title_label.text(), 'title')
-        self.header.show()
-        self.assertTrue(self.header.title_label.isVisible())
-        self.header.hide()
+        max_width = self.ui.max_title_width
+        text = build_string_for_pixels(self.ui.title_label, max_width)
+
+        self.ui.set_title(text)
+
+        self.assert_title_correct(self.ui.title_label, text, max_width)
 
     def test_set_elided_title(self):
         """Check if set_title adds the ellipsis when necessary."""
         # add an extra letter so we ensure this needs to be trimmed
-        title = 'a' * int(PREFERED_UI_SIZE['width'] * 0.95) + 'a'
-        self.header.set_title(title)
-        self.assertEqual(self.header.title_label.toolTip(), title)
-        expected = unicode(self.header.title_label.text())
-        self.assertTrue(expected.endswith(u'\u2026'))
+        max_width = self.ui.max_title_width
+        text = build_string_for_pixels(self.ui.title_label, max_width + 10)
+
+        self.ui.set_title(text)
+
+        self.assert_title_correct(self.ui.title_label, text, max_width)
 
     def test_set_empty_title(self):
         """Check if the widget is hidden for empty title."""
-        self.header.set_title('')
-        self.assertFalse(self.header.title_label.isVisible())
+        self.ui.set_title('')
+
+        self.assertEqual(self.ui.title_label.toolTip(), '')
+        self.assertFalse(self.ui.title_label.isVisible())
 
     def test_set_subtitle(self):
         """Check if set_subtitle works ok, showing the widget if necessary."""
-        self.header.set_subtitle('subtitle')
-        self.assertEqual(self.header.subtitle_label.text(), 'subtitle')
-        self.header.show()
-        self.assertTrue(self.header.subtitle_label.isVisible())
-        self.header.hide()
+        max_width = self.ui.max_subtitle_width
+        text = build_string_for_pixels(self.ui.subtitle_label, max_width)
+
+        self.ui.set_subtitle(text)
+
+        self.assert_subtitle_correct(self.ui.subtitle_label, text, max_width)
 
     def test_set_elided_subtitle(self):
         """Check if set_subtitle adds the ellipsis when necessary."""
-        subtitle = 'a' * int(PREFERED_UI_SIZE['width'] * 0.95) + 'a'
-        self.header.set_subtitle(subtitle)
-        self.assertEqual(self.header.subtitle_label.toolTip(), subtitle)
-        expected = unicode(self.header.subtitle_label.text())
-        self.assertTrue(expected.endswith(u'\u2026'))
+        max_width = self.ui.max_subtitle_width
+        text = build_string_for_pixels(self.ui.subtitle_label, max_width + 10)
+
+        self.ui.set_subtitle(text)
+
+        self.assert_subtitle_correct(self.ui.subtitle_label, text, max_width)
 
     def test_set_empty_subtitle(self):
         """Check if the widget is hidden for empty subtitle."""
-        self.header.set_title('')
-        self.assertFalse(self.header.title_label.isVisible())
-
-
-class SSOWizardPageTest(PageBaseTestCase):
-
-    """Tests for SSOWizardPage."""
-
-    ui_class = SetupAccountPage
+        self.ui.set_subtitle('')
+
+        self.assertEqual(self.ui.subtitle_label.toolTip(), '')
+        self.assertFalse(self.ui.subtitle_label.isVisible())
+
+
+class BaseWizardPageTestCase(PageBaseTestCase):
+
+    """Tests for SSOWizardPage."""
+
+    kwargs = {}
+    ui_class = gui.BaseWizardPage
+
+    def test_max_width(self):
+        """The max_width is correct."""
+        self.assertEqual(self.ui.max_width, 0)
+
+
+class SSOWizardPageTestCase(BaseWizardPageTestCase):
+
+    """Tests for SSOWizardPage."""
+
+    kwargs = dict(app_name=APP_NAME)
+    ui_class = gui.SSOWizardPage
+
+    def test_max_width(self):
+        """The max_width is correct."""
+        self.assertEqual(self.ui.max_width, PREFERED_UI_SIZE['width'])
 
     def test_show_error(self):
         """Test show_error with a normal length string."""
         message = 'error-message'
-        self.ui.show_error(self.app_name, message)
-        self.assertEqual(self.ui.form_errors_label.toolTip(), message)
-        expected = unicode(self.ui.form_errors_label.text())
-        self.assertEqual(expected, message)
+        self.ui.show_error(message)
+
+        self.assert_error_correct(self.ui.form_errors_label, message,
+                                  self.ui.header.max_title_width)
 
     def test_show_error_long_text(self):
         """Test show_error with a long length string."""
-        message = 'a' * int(PREFERED_UI_SIZE['width'] * 0.95) + 'a'
-        self.ui.show_error(self.app_name, message)
-        self.assertEqual(self.ui.form_errors_label.toolTip(), message)
-        expected = unicode(self.ui.form_errors_label.text())
-        self.assertTrue(expected.endswith(u'\u2026'))
+        message = build_string_for_pixels(self.ui.form_errors_label,
+                                          self.ui.header.max_title_width + 10)
+
+        self.ui.show_error(message)
+        self.assert_error_correct(self.ui.form_errors_label, message,
+                                  self.ui.header.max_title_width)
 
     def test_hide_error(self):
         """Test show_error with a long length string."""
-        message = ' '
         self.ui.hide_error()
-        self.assertEqual(self.ui.form_errors_label.text(), message)
+
+        self.assertEqual(self.ui.form_errors_label.text(), ' ')
+
+    def test_setup_page_with_failing_backend(self):
+        """Test how the ui react with an invalid backend."""
+        self.patch(gui.main, "get_sso_client", lambda: None)
+        self.patch(self.ui, "show_error", self._set_called)
+        self.ui.setup_page()
+        reason = 'There was a problem trying to setup the page %r' % self.ui
+        expected = ((reason,), {})
+        self.assertEqual(expected, self._called)
+        self.assertFalse(self.ui.isEnabled())

=== modified file 'ubuntu_sso/qt/tests/test_ubuntu_sso_wizard.py'
--- ubuntu_sso/qt/tests/test_ubuntu_sso_wizard.py	2012-03-05 14:37:43 +0000
+++ ubuntu_sso/qt/tests/test_ubuntu_sso_wizard.py	2012-03-20 16:10:09 +0000
@@ -19,6 +19,11 @@
 from PyQt4 import QtGui
 from twisted.internet import defer
 
+from ubuntu_sso.tests import (
+    APP_NAME,
+    EMAIL,
+    PASSWORD,
+)
 from ubuntu_sso.qt import PREFERED_UI_SIZE, ubuntu_sso_wizard
 from ubuntu_sso.qt.tests import (
     BaseTestCase,
@@ -86,6 +91,11 @@
         finish_button.clicked.emit(True)
         self.assertEqual(self._called, ((None,), {}))
 
+    def test_window_title(self):
+        """Check the window title for the application."""
+        title = unicode(self.ui.windowTitle())
+        self.assertEqual(title, ubuntu_sso_wizard.WINDOW_TITLE)
+
 
 class UbuntuSSOWizardTestCase(BaseTestCase):
 
@@ -102,13 +112,9 @@
 
     def test_window_size(self):
         """check the window size."""
-        self.ui.show()
-        self.addCleanup(self.ui.hide)
         size = self.ui.size()
-        # pylint: disable=E1101
-        self.assertGreaterEqual(size.height(), PREFERED_UI_SIZE['height'])
-        self.assertGreaterEqual(size.width(), PREFERED_UI_SIZE['width'])
-        # pylint: enable=E1101
+        self.assertTrue(size.height() >= PREFERED_UI_SIZE['height'])
+        self.assertTrue(size.width() >= PREFERED_UI_SIZE['width'])
 
     def test_move_to_success_page_state(self):
         """Test _move_to_success_page method."""
@@ -123,6 +129,9 @@
 
     def test_overlay_shows(self):
         """Test if the signals call the overlay.show properly."""
+        # reset the counter
+        self.ui.overlay.show_counter = 0
+
         for page in self.ui._pages:
             page.show_overlay()
 
@@ -130,6 +139,9 @@
 
     def test_overlay_hides(self):
         """Test if the signals call the overlay.show properly."""
+        # reset the counter
+        self.ui.overlay.show_counter = 0
+
         for page in self.ui._pages:
             page.hide_overlay()
 
@@ -146,3 +158,21 @@
         self.ui.reset_password.passwordChanged.emit('')
         expected = ((self.ui.current_user,), {})
         self.assertEqual(expected, self._called)
+
+    def test_email_verification_page_params_from_current_user(self):
+        """Tests that email_verification_page receives the proper params."""
+        self.ui._next_id = self.ui.current_user_page_id
+        self.ui.next()
+        self.ui.current_user.ui.email_edit.setText(EMAIL)
+        self.ui.current_user.ui.password_edit.setText(PASSWORD)
+        self.ui.current_user.on_user_not_validated(APP_NAME, EMAIL)
+        self.assertEqual(EMAIL, self.ui.email_verification.email)
+        self.assertEqual(PASSWORD, self.ui.email_verification.password)
+
+    def test_email_verification_page_params_from_setup(self):
+        """Tests that email_verification_page receives the proper params."""
+        self.ui.setup_account.ui.email_edit.setText(EMAIL)
+        self.ui.setup_account.ui.password_edit.setText(PASSWORD)
+        self.ui.setup_account.on_user_registered(APP_NAME, {})
+        self.assertEqual(EMAIL, self.ui.email_verification.email)
+        self.assertEqual(PASSWORD, self.ui.email_verification.password)

=== modified file 'ubuntu_sso/qt/ubuntu_sso_wizard.py'
--- ubuntu_sso/qt/ubuntu_sso_wizard.py	2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/ubuntu_sso_wizard.py	2012-03-20 16:10:09 +0000
@@ -30,7 +30,7 @@
     USER_SUCCESS,
 )
 from ubuntu_sso.logger import setup_gui_logging
-from ubuntu_sso.qt import PREFERED_UI_SIZE
+from ubuntu_sso.qt import PREFERED_UI_SIZE, WINDOW_TITLE
 from ubuntu_sso.qt.current_user_sign_in_page import CurrentUserSignInPage
 from ubuntu_sso.qt.email_verification_page import EmailVerificationPage
 from ubuntu_sso.qt.error_page import ErrorPage
@@ -141,6 +141,8 @@
 
     def _go_back_to_page(self, page):
         """Move back until it reaches the 'page'."""
+        logger.debug('Moving back from page: %s, to page: %s',
+            self.currentPage(), page)
         page_id = self._pages[page]
         visited_pages = self.visitedPages()
         for index in reversed(visited_pages):
@@ -150,30 +152,42 @@
 
     def _move_to_reset_password_page(self):
         """Move to the reset password page wizard."""
+        logger.debug('Moving to ResetPasswordPage from: %s',
+            self.currentPage())
         self._next_id = self.reset_password_page_id
         self.next()
         self._next_id = -1
 
-    def _move_to_email_verification_page(self):
+    def _move_to_email_verification_page(self, email):
         """Move to the email verification page wizard."""
+        logger.debug('Moving to EmailVerificationPage from: %s',
+            self.currentPage())
         self._next_id = self.email_verification_page_id
+        self.email_verification.email = unicode(email)
+        self.email_verification.password = self.currentPage().password
         self.next()
         self._next_id = -1
 
     def _move_to_setup_account_page(self):
         """Move to the setup account page wizard."""
+        logger.debug('Moving to SetupAccountPage from: %s',
+            self.currentPage())
         self._next_id = self.setup_account_page_id
         self.next()
         self._next_id = -1
 
     def _move_to_login_page(self):
         """Move to the login page wizard."""
+        logger.debug('Moving to CurrentUserSignInPage from: %s',
+            self.currentPage())
         self._next_id = self.current_user_page_id
         self.next()
         self._next_id = -1
 
     def _move_to_success_page(self):
         """Move to the success page wizard."""
+        logger.debug('Moving to SuccessPage from: %s',
+            self.currentPage())
         self._next_id = self.success_page_id
         self.next()
         self.setButtonLayout([
@@ -186,6 +200,8 @@
 
     def _move_to_forgotten_page(self):
         """Move to the forgotten page wizard."""
+        logger.debug('Moving to ForgottenPasswordPage from: %s',
+            self.currentPage())
         self._next_id = self.forgotten_password_page_id
         self.next()
         self._next_id = -1
@@ -258,6 +274,7 @@
         logger.debug('UbuntuSSOClientGUI: app_name %r, kwargs %r.',
                      app_name, kwargs)
         self.app_name = app_name
+        self.setWindowTitle(WINDOW_TITLE)
         # create the controller and the ui, then set the cb and call the show
         # method so that we can work
         self.wizard = UbuntuSSOWizard(app_name=app_name, **kwargs)

=== modified file 'ubuntu_sso/utils/__init__.py'
--- ubuntu_sso/utils/__init__.py	2012-02-17 20:48:27 +0000
+++ ubuntu_sso/utils/__init__.py	2012-03-20 16:10:09 +0000
@@ -32,8 +32,14 @@
 
 
 logger = setup_logging("ubuntu_sso.utils")
+BIN_SUFFIX = 'bin'
 DATA_SUFFIX = 'data'
-BIN_SUFFIX = 'bin'
+
+if sys.platform == "win32":
+    from ubuntu_sso.utils import windows as source
+else:
+    from ubuntu_sso.utils import linux as source
+PLATFORM_QSS = source.PLATFORM_QSS
 
 
 def _get_dir(dir_name, dir_constant):
@@ -88,8 +94,15 @@
     found, return the value of the BIN_DIR.
 
     """
-    result = _get_dir(dir_name=BIN_SUFFIX, dir_constant='BIN_DIR')
+    # If sys is frozen, this is an .exe, and all binaries are in
+    # the same place
+    if getattr(sys, "frozen", None) is not None:
+        exec_path = os.path.abspath(sys.executable)
+        result = os.path.dirname(exec_path)
+    else:
+        result = _get_dir(dir_name=BIN_SUFFIX, dir_constant='BIN_DIR')
     assert result is not None, '%r dir can not be None.' % BIN_SUFFIX
+    logger.info('get_bin_dir: returning dir located at %r.', result)
     return result
 
 

=== added file 'ubuntu_sso/utils/linux.py'
--- ubuntu_sso/utils/linux.py	1970-01-01 00:00:00 +0000
+++ ubuntu_sso/utils/linux.py	2012-03-20 16:10:09 +0000
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+#
+# 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
+# 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 <http://www.gnu.org/licenses/>.
+
+"""Platform specific constants and functions (for Linux)."""
+
+PLATFORM_QSS = ":/linux.qss"

=== modified file 'ubuntu_sso/utils/runner/glib.py'
--- ubuntu_sso/utils/runner/glib.py	2012-02-17 18:43:17 +0000
+++ ubuntu_sso/utils/runner/glib.py	2012-03-20 16:10:09 +0000
@@ -27,7 +27,7 @@
 logger = setup_logging("ubuntu_sso.utils.runner.glib")
 
 
-NO_SUCH_FILE_OR_DIR = 'No such file or directory'
+NO_SUCH_FILE_OR_DIR = '[Errno 2]'
 
 
 def spawn_program(args, reply_handler, error_handler):

=== modified file 'ubuntu_sso/utils/runner/tx.py'
--- ubuntu_sso/utils/runner/tx.py	2012-02-17 19:13:15 +0000
+++ ubuntu_sso/utils/runner/tx.py	2012-03-20 16:10:09 +0000
@@ -26,7 +26,7 @@
 
 logger = setup_logging("ubuntu_sso.utils.runner.tx")
 
-NO_SUCH_FILE_OR_DIR = 'OSError: [Errno 2] No such file or directory'
+NO_SUCH_FILE_OR_DIR = 'OSError: [Errno 2]'
 
 
 EXE_EXT = ''

=== modified file 'ubuntu_sso/utils/tests/test_common.py'
--- ubuntu_sso/utils/tests/test_common.py	2012-02-15 20:21:52 +0000
+++ ubuntu_sso/utils/tests/test_common.py	2012-03-20 16:10:09 +0000
@@ -17,6 +17,7 @@
 """Tests for the oauth_headers helper function."""
 
 import logging
+import os
 import sys
 import time
 
@@ -146,13 +147,21 @@
         self.assertEqual(expected, result)
 
 
-class GetBinDirTestCase(TestCase):
+class GetBinDirTestCase(GetProjectDirTestCase):
     """Test case for get_bin_dir when constants module is not defined."""
 
     DIR_NAME = utils.BIN_SUFFIX
     DIR_CONSTANT = 'BIN_DIR'
     DIR_GETTER = 'get_bin_dir'
 
+    def test_frozen_binary(self):
+        """Test that frozen binaries return a valid path."""
+        sys.frozen = True
+        self.addCleanup(delattr, sys, "frozen")
+        expected = os.path.dirname(os.path.abspath(sys.executable))
+        result = self.get_dir()
+        self.assertEqual(expected, result)
+
 
 class GetBinDirWithConstantsTestCase(GetProjectDirWithConstantsTestCase):
     """Test case for get_bin_dir when constants module is not defined."""

=== modified file 'ubuntu_sso/utils/ui.py'
--- ubuntu_sso/utils/ui.py	2012-03-06 14:13:10 +0000
+++ ubuntu_sso/utils/ui.py	2012-03-20 16:10:09 +0000
@@ -43,7 +43,7 @@
 CAPTCHA_RELOAD_TEXT = _('refresh')
 CAPTCHA_RELOAD_TOOLTIP = _('Reload')
 CAPTCHA_REQUIRED_ERROR = _('The captcha is a required field')
-CLOSE_AND_SETUP_LATER = _('Close window and setup later')
+CLOSE_AND_SETUP_LATER = _('Close window and set up later')
 CONGRATULATIONS = _("Congratulations, {app_name} is installed!")
 CONNECT_HELP_LABEL = _('To connect this computer to %(app_name)s enter your '
     'details below.')
@@ -133,6 +133,12 @@
 SSL_CERT_DETAILS = _('Certificate details')
 SSL_CONNECT_BUTTON = _('Connect')
 SSL_DETAILS_HELP = _('the details ssl certificate we are going to show.')
+SSL_DETAILS_TEMPLATE = ('Organization:\t%(organization)s\n'
+                        'Common Name:\t%(common_name)s\n'
+                        'Locality Name:\t%(locality_name)s\n'
+                        'Unit:\t%(unit)s\n'
+                        'Country:\t%(country_name)s\n'
+                        'State or Province:\t%(state_name)s')
 SSL_DESCRIPTION = _('Open the SSL certificate UI.')
 SSL_DIALOG_TITLE = _('SSL Certificate Not Valid')
 SSL_DOMAIN_HELP = _('the domain whose ssl certificate we are going to show.')

=== modified file 'ubuntu_sso/utils/webclient/__init__.py'
--- ubuntu_sso/utils/webclient/__init__.py	2012-02-09 18:11:02 +0000
+++ ubuntu_sso/utils/webclient/__init__.py	2012-03-20 16:10:09 +0000
@@ -19,6 +19,7 @@
 
 # pylint: disable=W0611
 from ubuntu_sso.utils.webclient.common import (
+    ProxyUnauthorizedError,
     UnauthorizedError,
     WebClientError,
 )
@@ -26,8 +27,23 @@
 
 def is_qt4reactor_installed():
     """Check if the qt4reactor is installed."""
-    reactor = sys.modules.get("twisted.internet.reactor")
-    return reactor and getattr(reactor, "qApp", None)
+    result = False
+
+    if not 'PyQt4' in sys.modules:
+        return result
+
+    try:
+        from PyQt4.QtCore import QCoreApplication
+        from PyQt4.QtGui import QApplication
+
+        # we could be running a process with or without ui, and those are diff
+        # apps.
+        result = (QCoreApplication.instance() is not None
+                   or QApplication.instance() is not None)
+    except ImportError:
+        pass
+
+    return result
 
 
 def webclient_module():

=== modified file 'ubuntu_sso/utils/webclient/common.py'
--- ubuntu_sso/utils/webclient/common.py	2012-02-09 18:11:02 +0000
+++ ubuntu_sso/utils/webclient/common.py	2012-03-20 16:10:09 +0000
@@ -17,14 +17,23 @@
 
 import cgi
 import collections
+import os
 
 from httplib2 import iri2uri
 from oauth import oauth
 from twisted.internet import defer
 from urlparse import urlparse
 
+from ubuntu_sso import USER_SUCCESS, UI_PROXY_CREDS_DIALOG
+from ubuntu_sso.logger import setup_logging
+from ubuntu_sso.utils.runner import spawn_program
+from ubuntu_sso.utils.ui import SSL_DETAILS_TEMPLATE
 from ubuntu_sso.utils.webclient.timestamp import TimestampChecker
 
+SSL_DIALOG = 'ubuntu-sso-ssl-certificate-qt'
+
+logger = setup_logging("ubuntu_sso.utils.webclient.common")
+
 
 class WebClientError(Exception):
     """An http error happened while calling the webservice."""
@@ -34,8 +43,12 @@
     """The request ended with bad_request, unauthorized or forbidden."""
 
 
+class ProxyUnauthorizedError(WebClientError):
+    """Failure raised when there is an issue with the proxy auth."""
+
+
 class Response(object):
-    """A reponse object."""
+    """A response object."""
 
     def __init__(self, content, headers=None):
         """Initialize this instance."""
@@ -77,10 +90,14 @@
 
     timestamp_checker = None
 
-    def __init__(self, username=None, password=None, oauth_sign_plain=False):
+    def __init__(self, appname='', username=None, password=None,
+                 oauth_sign_plain=False):
         """Initialize this instance."""
+        self.appname = appname
         self.username = username
         self.password = password
+        self.proxy_username = None
+        self.proxy_password = None
         self.oauth_sign_plain = oauth_sign_plain
 
     def request(self, iri, method="GET", extra_headers=None,
@@ -154,3 +171,94 @@
 
     def shutdown(self):
         """Shut down all pending requests (if possible)."""
+
+    @defer.inlineCallbacks
+    def _load_proxy_creds_from_keyring(self, domain):
+        """Load the proxy creds from the keyring."""
+        from ubuntu_sso.keyring import Keyring
+        keyring = Keyring()
+        try:
+            creds = yield keyring.get_credentials(str(domain))
+            logger.debug('Got credentials from keyring.')
+        except Exception, e:
+            logger.error('Error when retrieving the creds.')
+            raise WebClientError('Error when retrieving the creds.', e)
+        if creds is not None:
+            # if we are loading the same creds it means that we got the wrong
+            # ones
+            if (self.proxy_username == creds['username'] and
+                    self.proxy_password == creds['password']):
+                defer.returnValue(False)
+            else:
+                self.proxy_username = creds['username']
+                self.proxy_password = creds['password']
+                defer.returnValue(True)
+        logger.debug('Proxy creds not in keyring.')
+        defer.returnValue(False)
+
+    def _launch_proxy_creds_dialog(self, domain, retry):
+        """Launch the dialog used to get the creds."""
+        from ubuntu_sso.utils import get_bin_dir
+
+        bin_dir = get_bin_dir()
+        args = (os.path.join(bin_dir, UI_PROXY_CREDS_DIALOG), '--domain',
+                                                                  domain)
+        if retry:
+            args += ('--retry',)
+        return spawn_program(args)
+
+    @defer.inlineCallbacks
+    def request_proxy_auth_credentials(self, domain, retry):
+        """Request the auth creds to the user."""
+        if not retry:
+            if (self.proxy_username is not None
+                    and self.proxy_password is not None):
+                logger.debug('Not retry and credentials are present.')
+                defer.returnValue(True)
+            else:
+                creds_loaded = yield self._load_proxy_creds_from_keyring(
+                                                                      domain)
+                if creds_loaded:
+                    defer.returnValue(True)
+
+        try:
+            return_code = yield self._launch_proxy_creds_dialog(domain, retry)
+        except Exception, e:
+            logger.error('Error when running external ui process.')
+            raise WebClientError('Error when running external ui process.', e)
+
+        if return_code == USER_SUCCESS:
+            creds_loaded = yield self._load_proxy_creds_from_keyring(domain)
+            defer.returnValue(creds_loaded)
+        else:
+            logger.debug('Could not retrieve the credentials. Return code: %r',
+                         return_code)
+            defer.returnValue(False)
+
+    def format_ssl_details(self, details):
+        """Return a formatted string with the details."""
+        return SSL_DETAILS_TEMPLATE % details
+
+    def _launch_ssl_dialog(self, domain, details):
+        """Launch a dialog used to approve the ssl cert."""
+        from ubuntu_sso.utils import get_bin_dir
+
+        bin_dir = get_bin_dir()
+        args = (os.path.join(bin_dir, SSL_DIALOG), '--domain', domain,
+                                                   '--details', details,
+                                                   '--appname', self.appname)
+        return spawn_program(args)
+
+    def _was_ssl_accepted(self, cert_details):
+        """Return if the cert was already accepted."""
+        # TODO: Ensure that we look at pinned certs in a following branch
+        return False
+
+    @defer.inlineCallbacks
+    def request_ssl_cert_approval(self, domain, details):
+        """Request the user for ssl approval."""
+        if self._was_ssl_accepted(details):
+            defer.returnValue(True)
+
+        return_code = yield self._launch_ssl_dialog(domain, details)
+        defer.returnValue(return_code == USER_SUCCESS)

=== modified file 'ubuntu_sso/utils/webclient/gsettings.py'
--- ubuntu_sso/utils/webclient/gsettings.py	2012-02-10 14:46:16 +0000
+++ ubuntu_sso/utils/webclient/gsettings.py	2012-03-20 16:10:09 +0000
@@ -31,6 +31,24 @@
     return hostname, username, password
 
 
+def parse_manual_proxy_settings(scheme, gsettings):
+    """Parse the settings for a given scheme."""
+    host, username, pwd = parse_proxy_host(gsettings[scheme + ".host"])
+    settings = {
+        "host": host,
+        "port": gsettings[scheme + ".port"],
+    }
+    if scheme == "http" and gsettings["http.use-authentication"]:
+        username = gsettings["http.authentication-user"]
+        pwd = gsettings["http.authentication-password"]
+    if username is not None and pwd is not None:
+        settings.update({
+            "username": username,
+            "password": pwd,
+        })
+    return settings
+
+
 def get_proxy_settings():
     """Parse the proxy settings as returned by the gsettings executable."""
     output = subprocess.check_output(GSETTINGS_CMDLINE.split())
@@ -56,20 +74,9 @@
     if mode == "none":
         settings = {}
     elif mode == "manual":
-        # attempt to parse the host
-        host, username, pwd = parse_proxy_host(gsettings["http.host"])
-        settings = {
-            "host": host,
-            "port": gsettings["http.port"],
-        }
-        if gsettings["http.use-authentication"]:
-            username = gsettings["http.authentication-user"]
-            pwd = gsettings["http.authentication-password"]
-        if username is not None and pwd is not None:
-            settings.update({
-                "username": username,
-                "password": pwd,
-            })
+        settings = {}
+        for scheme in ["http", "https"]:
+            settings[scheme] = parse_manual_proxy_settings(scheme, gsettings)
     else:
         # If mode is automatic the PAC javascript should be interpreted
         # on each request. That is out of scope so it's ignored for now

=== modified file 'ubuntu_sso/utils/webclient/libsoup.py'
--- ubuntu_sso/utils/webclient/libsoup.py	2012-02-07 19:36:50 +0000
+++ ubuntu_sso/utils/webclient/libsoup.py	2012-03-20 16:10:09 +0000
@@ -19,10 +19,12 @@
 
 from twisted.internet import defer
 
+from ubuntu_sso.logger import setup_logging
 from ubuntu_sso.utils.webclient.common import (
     BaseWebClient,
     HeaderDict,
     Response,
+    ProxyUnauthorizedError,
     UnauthorizedError,
     WebClientError,
 )
@@ -30,6 +32,8 @@
 URI_ANONYMOUS_TEMPLATE = "http://{host}:{port}/"
 URI_USERNAME_TEMPLATE = "http://{username}:{password}@{host}:{port}/"
 
+logger = setup_logging("ubuntu_sso.utils.webclient.libsoup")
+
 
 class WebClient(BaseWebClient):
     """A webclient with a libsoup backend."""
@@ -41,11 +45,12 @@
         from gi.repository import Soup, SoupGNOME
         self.soup = Soup
         self.session = Soup.SessionAsync()
-        self.session.add_feature_by_type(SoupGNOME.ProxyResolverGNOME)
+        self.session.add_feature(SoupGNOME.ProxyResolverGNOME())
         self.session.connect("authenticate", self._on_authenticate)
 
     def _on_message(self, session, message, d):
         """Handle the result of an http message."""
+        logger.debug('_on_message status code is %s', message.status_code)
         if message.status_code == httplib.OK:
             headers = HeaderDict()
             response_headers = message.get_property("response-headers")
@@ -57,14 +62,51 @@
         elif message.status_code == httplib.UNAUTHORIZED:
             e = UnauthorizedError(message.reason_phrase)
             d.errback(e)
+        elif message.status_code == httplib.PROXY_AUTHENTICATION_REQUIRED:
+            e = ProxyUnauthorizedError(message.reason_phrase)
+            d.errback(e)
         else:
             e = WebClientError(message.reason_phrase)
             d.errback(e)
 
-    def _on_authenticate(self, sesion, message, auth, retrying, data=None):
+    @defer.inlineCallbacks
+    def _on_authenticate(self, session, message, auth, retrying, data=None):
         """Handle the "authenticate" signal."""
-        if not retrying and self.username and self.password:
-            auth.authenticate(self.username, self.password)
+        self.session.pause_message(message)
+        try:
+            logger.debug('_on_authenticate: message status code is %s',
+                         message.status_code)
+            if not retrying and self.username and self.password:
+                auth.authenticate(self.username, self.password)
+            if auth.is_for_proxy():
+                logger.debug('_on_authenticate auth is for proxy.')
+                got_creds = yield self.request_proxy_auth_credentials(
+                                             self.session.props.proxy_uri.host,
+                                             retrying)
+                if got_creds:
+                    logger.debug('Got proxy credentials from user.')
+                    auth.authenticate(self.proxy_username, self.proxy_password)
+        finally:
+            self.session.unpause_message(message)
+
+    @defer.inlineCallbacks
+    def _on_proxy_authenticate(self, failure, iri, method="GET",
+                extra_headers=None, oauth_credentials=None, post_content=None):
+        """Deal with wrong settings."""
+        failure.trap(ProxyUnauthorizedError)
+        logger.debug('Proxy settings are wrong.')
+        got_creds = yield self.request_proxy_auth_credentials(
+                                           self.session.props.proxy_uri.host,
+                                           True)
+        if got_creds:
+            settings = dict(host=self.session.props.proxy_uri.host,
+                            port=self.session.props.proxy_uri.port,
+                            username=self.proxy_username,
+                            password=self.proxy_password)
+            self.force_use_proxy(settings)
+            response = yield self.request(iri, method, extra_headers,
+                                         oauth_credentials, post_content)
+            defer.returnValue(response)
 
     @defer.inlineCallbacks
     def request(self, iri, method="GET", extra_headers=None,
@@ -92,6 +134,8 @@
             message.request_body.append(post_content)
 
         self.session.queue_message(message, self._on_message, d)
+        d.addErrback(self._on_proxy_authenticate, iri, method, extra_headers,
+                     oauth_credentials, post_content)
         response = yield d
         defer.returnValue(response)
 

=== modified file 'ubuntu_sso/utils/webclient/qtnetwork.py'
--- ubuntu_sso/utils/webclient/qtnetwork.py	2012-02-07 19:36:50 +0000
+++ ubuntu_sso/utils/webclient/qtnetwork.py	2012-03-20 16:10:09 +0000
@@ -30,29 +30,61 @@
     QNetworkProxyFactory,
     QNetworkReply,
     QNetworkRequest,
+    QSslCertificate,
 )
 from twisted.internet import defer
 
+from ubuntu_sso.logger import setup_logging
 from ubuntu_sso.utils.webclient.common import (
     BaseWebClient,
     HeaderDict,
+    ProxyUnauthorizedError,
     Response,
     UnauthorizedError,
     WebClientError,
 )
 from ubuntu_sso.utils.webclient import gsettings
 
+logger = setup_logging("ubuntu_sso.utils.webclient.qtnetwork")
+
+
+def build_proxy(settings_groups):
+    """Create a QNetworkProxy from these settings."""
+    proxy_groups = [
+        ("socks", QNetworkProxy.Socks5Proxy),
+        ("https", QNetworkProxy.HttpProxy),
+        ("http", QNetworkProxy.HttpProxy),
+    ]
+    for group, proxy_type in proxy_groups:
+        if group not in settings_groups:
+            continue
+        settings = settings_groups[group]
+        if "host" in settings and "port" in settings:
+            return QNetworkProxy(proxy_type,
+                                 hostName=settings.get("host", ""),
+                                 port=settings.get("port", 0),
+                                 user=settings.get("username", ""),
+                                 password=settings.get("password", ""))
+    logger.error("No proxy correctly configured.")
+    return QNetworkProxy(QNetworkProxy.DefaultProxy)
+
 
 class WebClient(BaseWebClient):
     """A webclient with a qtnetwork backend."""
 
+    proxy_instance = None
+
     def __init__(self, *args, **kwargs):
         """Initialize this instance."""
         super(WebClient, self).__init__(*args, **kwargs)
         self.nam = QNetworkAccessManager(QCoreApplication.instance())
         self.nam.finished.connect(self._handle_finished)
         self.nam.authenticationRequired.connect(self._handle_authentication)
+        self.nam.proxyAuthenticationRequired.connect(self.handle_proxy_auth)
+        # Disabled until we make this a per-instance option
+        #self.nam.sslErrors.connect(self._handle_ssl_errors)
         self.replies = {}
+        self.proxy_retry = False
         self.setup_proxy()
 
     def setup_proxy(self):
@@ -60,11 +92,43 @@
         # QtNetwork knows how to use the system settings on both Win and Mac
         if sys.platform.startswith("linux"):
             settings = gsettings.get_proxy_settings()
-            if settings:
-                self.force_use_proxy(settings)
+            enabled = len(settings) > 0
+            if enabled and WebClient.proxy_instance is None:
+                proxy = build_proxy(settings)
+                QNetworkProxy.setApplicationProxy(proxy)
+                WebClient.proxy_instance = proxy
+            elif enabled and WebClient.proxy_instance:
+                logger.info("Proxy already in use.")
+            else:
+                logger.info("Proxy is disabled.")
         else:
             QNetworkProxyFactory.setUseSystemConfiguration(True)
 
+    def handle_proxy_auth(self, proxy, authenticator):
+        """Proxy authentication is required."""
+        logger.info("auth_required %r, %r", self.proxy_username,
+                                            proxy.hostName())
+        if (self.proxy_username is not None and
+                self.proxy_username != str(authenticator.user())):
+            authenticator.setUser(self.proxy_username)
+            WebClient.proxy_instance.setUser(self.proxy_username)
+        if (self.proxy_password is not None and
+                self.proxy_password != str(authenticator.password())):
+            authenticator.setPassword(self.proxy_password)
+            WebClient.proxy_instance.setPassword(self.proxy_password)
+
+    def _perform_request(self, request, method, post_buffer):
+        """Return a deferred that will be fired with a Response object."""
+        d = defer.Deferred()
+        if method == "GET":
+            reply = self.nam.get(request)
+        elif method == "HEAD":
+            reply = self.nam.head(request)
+        else:
+            reply = self.nam.sendCustomRequest(request, method, post_buffer)
+        self.replies[reply] = d
+        return d
+
     @defer.inlineCallbacks
     def request(self, iri, method="GET", extra_headers=None,
                 oauth_credentials=None, post_content=None):
@@ -86,23 +150,30 @@
         for key, value in headers.iteritems():
             request.setRawHeader(key, value)
 
-        d = defer.Deferred()
-        if method == "GET":
-            reply = self.nam.get(request)
-        elif method == "HEAD":
-            reply = self.nam.head(request)
-        else:
-            post_buffer = QBuffer()
-            post_buffer.setData(post_content)
-            reply = self.nam.sendCustomRequest(request, method, post_buffer)
-        self.replies[reply] = d
-        result = yield d
+        post_buffer = QBuffer()
+        post_buffer.setData(post_content)
+        try:
+            result = yield self._perform_request(request, method, post_buffer)
+        except ProxyUnauthorizedError, e:
+            app_proxy = QNetworkProxy.applicationProxy()
+            proxy_host = app_proxy.hostName() if app_proxy else "proxy server"
+            got_creds = yield self.request_proxy_auth_credentials(
+                                            proxy_host, self.proxy_retry)
+            if got_creds:
+                self.proxy_retry = True
+                result = yield self.request(iri, method, extra_headers,
+                                            oauth_credentials, post_content)
+            else:
+                excp = WebClientError('Proxy creds needed.', e)
+                defer.returnValue(excp)
         defer.returnValue(result)
 
     def _handle_authentication(self, reply, authenticator):
         """The reply needs authentication."""
-        authenticator.setUser(self.username)
-        authenticator.setPassword(self.password)
+        if authenticator.user() != self.username:
+            authenticator.setUser(self.username)
+        if authenticator.password() != self.password:
+            authenticator.setPassword(self.password)
 
     def _handle_finished(self, reply):
         """The reply has finished processing."""
@@ -118,20 +189,51 @@
             d.callback(response)
         else:
             error_string = reply.errorString()
+            logger.debug('_handle_finished error (%s,%s).', error,
+                         error_string)
             if error == QNetworkReply.AuthenticationRequiredError:
                 exception = UnauthorizedError(error_string, content)
+            elif error == QNetworkReply.ProxyAuthenticationRequiredError:
+                # we are going thru a proxy and we did not auth
+                exception = ProxyUnauthorizedError(error_string, content)
             else:
                 exception = WebClientError(error_string, content)
             d.errback(exception)
 
-    def force_use_proxy(self, settings):
+    def _get_certificate_details(self, cert):
+        """Return an string with the details of the certificate."""
+        detail_titles = {QSslCertificate.Organization: 'organization',
+                         QSslCertificate.CommonName: 'common_name',
+                         QSslCertificate.LocalityName: 'locality_name',
+                         QSslCertificate.OrganizationalUnitName: 'unit',
+                         QSslCertificate.CountryName: 'country_name',
+                         QSslCertificate.StateOrProvinceName: 'state_name'}
+        details = {}
+        for info, title in detail_titles.iteritems():
+            details[title] = str(cert.issuerInfo(info))
+        return self.format_ssl_details(details)
+
+    def _get_certificate_host(self, cert):
+        """Return the host of the cert."""
+        return str(cert.issuerInfo(QSslCertificate.CommonName))
+
+    @defer.inlineCallbacks
+    def _handle_ssl_errors(self, reply, errors):
+        """Handle the case in which we got an ssl error."""
+        # ask the user if the cer should be trusted
+        cert = errors[0].certificate()
+        trust_cert = yield self.request_ssl_cert_approval(
+                                    self._get_certificate_host(cert),
+                                    self._get_certificate_details(cert))
+        if trust_cert:
+            reply.ignoreSslErrors()
+
+    def force_use_proxy(self, https_settings):
         """Setup this webclient to use the given proxy settings."""
-        proxy = QNetworkProxy(QNetworkProxy.HttpProxy,
-                              hostName=settings.get("host", ""),
-                              port=settings.get("port", 0),
-                              user=settings.get("username", ""),
-                              password=settings.get("password", ""))
-        self.nam.setProxy(proxy)
+        settings = {"https": https_settings}
+        proxy = build_proxy(settings)
+        QNetworkProxy.setApplicationProxy(proxy)
+        WebClient.proxy_instance = proxy
 
     def shutdown(self):
         """Shut down all pending requests (if possible)."""

=== modified file 'ubuntu_sso/utils/webclient/tests/__init__.py'
--- ubuntu_sso/utils/webclient/tests/__init__.py	2012-02-07 19:36:50 +0000
+++ ubuntu_sso/utils/webclient/tests/__init__.py	2012-03-20 16:10:09 +0000
@@ -17,9 +17,48 @@
 """Tests for the proxy-aware webclient."""
 
 from twisted.application import internet, service
+from twisted.internet import ssl
 from twisted.web import http, server
 
 
+# Some settings are not used as described in:
+# https://bugzilla.gnome.org/show_bug.cgi?id=648237
+
+TEMPLATE_GSETTINGS_OUTPUT = """\
+org.gnome.system.proxy autoconfig-url '{autoconfig_url}'
+org.gnome.system.proxy ignore-hosts {ignore_hosts:s}
+org.gnome.system.proxy mode '{mode}'
+org.gnome.system.proxy.ftp host '{ftp_host}'
+org.gnome.system.proxy.ftp port {ftp_port}
+org.gnome.system.proxy.http authentication-password '{auth_password}'
+org.gnome.system.proxy.http authentication-user '{auth_user}'
+org.gnome.system.proxy.http host '{http_host}'
+org.gnome.system.proxy.http port {http_port}
+org.gnome.system.proxy.http use-authentication {http_use_auth}
+org.gnome.system.proxy.https host '{https_host}'
+org.gnome.system.proxy.https port {https_port}
+org.gnome.system.proxy.socks host '{socks_host}'
+org.gnome.system.proxy.socks port {socks_port}
+"""
+
+BASE_GSETTINGS_VALUES = {
+    "autoconfig_url": "",
+    "ignore_hosts": ["localhost", "127.0.0.0/8"],
+    "mode": "none",
+    "ftp_host": "",
+    "ftp_port": 0,
+    "auth_password": "",
+    "auth_user": "",
+    "http_host": "",
+    "http_port": 0,
+    "http_use_auth": "false",
+    "https_host": "",
+    "https_port": 0,
+    "socks_host": "",
+    "socks_port": 0,
+}
+
+
 class SaveHTTPChannel(http.HTTPChannel):
     """A save protocol to be used in tests."""
 
@@ -48,15 +87,26 @@
 class BaseMockWebServer(object):
     """A mock webserver for testing"""
 
-    def __init__(self):
+    def __init__(self, ssl_settings=None):
         """Start up this instance."""
         self.root = self.get_root_resource()
         self.site = SaveSite(self.root)
         application = service.Application('web')
         self.service_collection = service.IServiceCollection(application)
         #pylint: disable=E1101
-        self.tcpserver = internet.TCPServer(0, self.site)
-        self.tcpserver.setServiceParent(self.service_collection)
+        ssl_context = None
+        if (ssl_settings is not None
+                and 'key' in ssl_settings
+                and 'cert' in ssl_settings):
+            ssl_context = ssl.DefaultOpenSSLContextFactory(ssl_settings['key'],
+                                                         ssl_settings['cert'])
+            self.ssl_server = internet.SSLServer(0, self.site, ssl_context)
+        else:
+            self.ssl_server = None
+        self.server = internet.TCPServer(0, self.site)
+        self.server.setServiceParent(self.service_collection)
+        if self.ssl_server:
+            self.ssl_server.setServiceParent(self.service_collection)
         self.service_collection.startService()
 
     def get_root_resource(self):
@@ -65,9 +115,27 @@
 
     def get_iri(self):
         """Build the iri for this mock server."""
-        #pylint: disable=W0212
-        port_num = self.tcpserver._port.getHost().port
-        return u"http://127.0.0.1:%d/" % port_num
+        url = u"http://127.0.0.1:%d/"
+        return url % self.get_port()
+
+    def get_ssl_iri(self):
+        """Build the ssl iri for this mock server."""
+        if self.ssl_server:
+            url = u"https://127.0.0.1:%d/"
+            return url % self.get_ssl_port()
+
+    def get_port(self):
+        """Return the port where we are listening."""
+        # pylint: disable=W0212
+        return self.server._port.getHost().port
+        # pylint: enable=W0212
+
+    def get_ssl_port(self):
+        """Return the ssl port where we are listening."""
+        # pylint: disable=W0212
+        if self.ssl_server:
+            return self.ssl_server._port.getHost().port
+        # pylint: enable=W0212
 
     def stop(self):
         """Shut it down."""

=== modified file 'ubuntu_sso/utils/webclient/tests/test_gsettings.py'
--- ubuntu_sso/utils/webclient/tests/test_gsettings.py	2012-02-10 19:20:33 +0000
+++ ubuntu_sso/utils/webclient/tests/test_gsettings.py	2012-03-20 16:10:09 +0000
@@ -18,43 +18,10 @@
 from twisted.trial.unittest import TestCase
 
 from ubuntu_sso.utils.webclient import gsettings
-
-# Some settings are not used as described in:
-# https://bugzilla.gnome.org/show_bug.cgi?id=648237
-
-TEMPLATE_GSETTINGS_OUTPUT = """\
-org.gnome.system.proxy autoconfig-url '{autoconfig_url}'
-org.gnome.system.proxy ignore-hosts {ignore_hosts:s}
-org.gnome.system.proxy mode '{mode}'
-org.gnome.system.proxy.ftp host '{ftp_host}'
-org.gnome.system.proxy.ftp port {ftp_port}
-org.gnome.system.proxy.http authentication-password '{auth_password}'
-org.gnome.system.proxy.http authentication-user '{auth_user}'
-org.gnome.system.proxy.http host '{http_host}'
-org.gnome.system.proxy.http port {http_port}
-org.gnome.system.proxy.http use-authentication {http_use_auth}
-org.gnome.system.proxy.https host '{https_host}'
-org.gnome.system.proxy.https port {https_port}
-org.gnome.system.proxy.socks host '{socks_host}'
-org.gnome.system.proxy.socks port {socks_port}
-"""
-
-BASE_GSETTINGS_VALUES = {
-    "autoconfig_url": "",
-    "ignore_hosts": ["localhost", "127.0.0.0/8"],
-    "mode": "none",
-    "ftp_host": "",
-    "ftp_port": 0,
-    "auth_password": "",
-    "auth_user": "",
-    "http_host": "",
-    "http_port": 0,
-    "http_use_auth": "false",
-    "https_host": "",
-    "https_port": 0,
-    "socks_host": "",
-    "socks_port": 0,
-}
+from ubuntu_sso.utils.webclient.tests import (
+    BASE_GSETTINGS_VALUES,
+    TEMPLATE_GSETTINGS_OUTPUT,
+)
 
 
 class ProxySettingsTestCase(TestCase):
@@ -83,25 +50,33 @@
         ps = gsettings.get_proxy_settings()
         self.assertEqual(ps, expected)
 
+    def _assert_parser_anonymous(self, scheme):
+        """Assert the parsing of anonymous settings."""
+        template_values = dict(BASE_GSETTINGS_VALUES)
+        expected_host = "expected_host"
+        expected_port = 54321
+        expected = {
+            "host": expected_host,
+            "port": expected_port,
+        }
+        template_values.update({
+            "mode": "manual",
+            scheme + "_host": expected_host,
+            scheme + "_port": expected_port,
+        })
+        fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
+        self.patch(gsettings.subprocess, "check_output",
+                   lambda _: fake_output)
+        ps = gsettings.get_proxy_settings()
+        self.assertEqual(ps[scheme], expected)
+
     def test_gsettings_parser_http_anonymous(self):
         """Test a parser of gsettings."""
-        template_values = dict(BASE_GSETTINGS_VALUES)
-        expected_host = "expected_host"
-        expected_port = 54321
-        expected = {
-            "host": expected_host,
-            "port": expected_port,
-        }
-        template_values.update({
-            "mode": "manual",
-            "http_host": expected_host,
-            "http_port": expected_port,
-        })
-        fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
-        self.patch(gsettings.subprocess, "check_output",
-                   lambda _: fake_output)
-        ps = gsettings.get_proxy_settings()
-        self.assertEqual(ps, expected)
+        self._assert_parser_anonymous('http')
+
+    def test_gsettings_parser_https_anonymus(self):
+        """Test a parser of gsettings."""
+        self._assert_parser_anonymous('https')
 
     def test_gsettings_parser_http_authenticated(self):
         """Test a parser of gsettings."""
@@ -128,9 +103,9 @@
         self.patch(gsettings.subprocess, "check_output",
                    lambda _: fake_output)
         ps = gsettings.get_proxy_settings()
-        self.assertEqual(ps, expected)
+        self.assertEqual(ps["http"], expected)
 
-    def test_gsettings_parser_authenticated_url(self):
+    def _assert_parser_authenticated_url(self, scheme):
         """Test a parser of gsettings with creds in the url."""
         template_values = dict(BASE_GSETTINGS_VALUES)
         expected_host = "expected_host"
@@ -147,15 +122,23 @@
         }
         template_values.update({
             "mode": "manual",
-            "http_host": composed_url,
-            "http_port": expected_port,
+            scheme + "_host": composed_url,
+            scheme + "_port": expected_port,
             "http_use_auth": "false",
         })
         fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
         self.patch(gsettings.subprocess, "check_output",
                    lambda _: fake_output)
         ps = gsettings.get_proxy_settings()
-        self.assertEqual(ps, expected)
+        self.assertEqual(ps[scheme], expected)
+
+    def test_gsettings_parser_http_authenticated_url(self):
+        """Test a parser of gsettings with creds in the url."""
+        self._assert_parser_authenticated_url('http')
+
+    def test_gsettings_parser_https_authenticated_url(self):
+        """Test a parser of gsettings with creds in the url."""
+        self._assert_parser_authenticated_url('https')
 
     def test_gsettings_auth_over_url(self):
         """Test that the settings are more important that the url."""
@@ -166,7 +149,7 @@
         expected_password = "very secret password"
         composed_url = '%s:%s@%s' % ('user', 'random',
                                      expected_host)
-        expected = {
+        http_expected = {
             "host": expected_host,
             "port": expected_port,
             "username": expected_user,
@@ -184,7 +167,7 @@
         self.patch(gsettings.subprocess, "check_output",
                    lambda _: fake_output)
         ps = gsettings.get_proxy_settings()
-        self.assertEqual(ps, expected)
+        self.assertEqual(ps["http"], http_expected)
 
 
 class ParseProxyHostTestCase(TestCase):

=== modified file 'ubuntu_sso/utils/webclient/tests/test_webclient.py'
--- ubuntu_sso/utils/webclient/tests/test_webclient.py	2012-02-13 13:14:18 +0000
+++ ubuntu_sso/utils/webclient/tests/test_webclient.py	2012-03-20 16:10:09 +0000
@@ -16,9 +16,12 @@
 """Integration tests for the proxy-enabled webclient."""
 
 import os
+import shutil
 import sys
 import urllib
 
+from OpenSSL import crypto
+from socket import gethostname
 from twisted.cred import checkers, portal
 from twisted.internet import defer
 from twisted.web import guard, http, resource
@@ -27,7 +30,15 @@
 from ubuntuone.devtools.testcases import TestCase
 from ubuntuone.devtools.testcases.squid import SquidTestCase
 
+from ubuntu_sso import (
+    keyring,
+    EXCEPTION_RAISED,
+    USER_SUCCESS,
+    USER_CANCELLATION,
+)
 from ubuntu_sso.utils import webclient
+from ubuntu_sso.utils.ui import SSL_DETAILS_TEMPLATE
+from ubuntu_sso.utils.webclient import gsettings, txweb
 from ubuntu_sso.utils.webclient.common import BaseWebClient, HeaderDict, oauth
 from ubuntu_sso.utils.webclient.tests import BaseMockWebServer
 
@@ -188,9 +199,13 @@
         return root
 
 
-class FakeReactor(object):
-    """A fake reactor object."""
-    qApp = "Sample qapp"
+class FakeQApplication(object):
+    """A fake Qt module."""
+
+    @classmethod
+    def instance(cls):
+        """Return the instance."""
+        return cls
 
 
 class ModuleSelectionTestCase(TestCase):
@@ -201,10 +216,11 @@
         self.patch(sys, "modules", {})
         self.assertFalse(webclient.is_qt4reactor_installed())
 
-    def test_is_qt4reactor_installed_installed(self):
+    def test_is_qt4reactor_installed_installed_core(self):
         """When the qt4reactor is installed, it returns true."""
-        fake_sysmodules = {"twisted.internet.reactor": FakeReactor()}
-        self.patch(sys, "modules", fake_sysmodules)
+        from PyQt4 import QtCore
+
+        self.patch(QtCore, 'QCoreApplication', FakeQApplication)
         self.assertTrue(webclient.is_qt4reactor_installed())
 
     def assert_module_name(self, module, expected_name):
@@ -230,6 +246,7 @@
     """Test for the webclient."""
 
     timeout = 1
+    webclient_factory = webclient.webclient_factory
 
     @defer.inlineCallbacks
     def setUp(self):
@@ -237,7 +254,7 @@
         self.ws = MockWebServer()
         self.addCleanup(self.ws.stop)
         self.base_iri = self.ws.get_iri()
-        self.wc = webclient.webclient_factory()
+        self.wc = self.webclient_factory()
         self.addCleanup(self.wc.shutdown)
 
     @defer.inlineCallbacks
@@ -306,13 +323,22 @@
     @defer.inlineCallbacks
     def test_send_basic_auth(self):
         """The basic authentication headers are sent."""
-        other_wc = webclient.webclient_factory(username=SAMPLE_USERNAME,
-                                               password=SAMPLE_PASSWORD)
+        other_wc = self.webclient_factory(username=SAMPLE_USERNAME,
+                                          password=SAMPLE_PASSWORD)
         self.addCleanup(other_wc.shutdown)
         result = yield other_wc.request(self.base_iri + GUARDED)
         self.assertEqual(SAMPLE_RESOURCE, result.content)
 
     @defer.inlineCallbacks
+    def test_send_basic_auth_wrong_credentials(self):
+        """Wrong credentials returns a webclient error."""
+        other_wc = self.webclient_factory(username=SAMPLE_USERNAME,
+                                          password="wrong password!")
+        self.addCleanup(other_wc.shutdown)
+        yield self.assertFailure(other_wc.request(self.base_iri + GUARDED),
+                                 webclient.UnauthorizedError)
+
+    @defer.inlineCallbacks
     def test_request_is_oauth_signed(self):
         """The request is oauth signed."""
         tsc = self.wc.get_timestamp_checker()
@@ -348,6 +374,57 @@
                         "The type of %r must be bytes" % result.content)
 
 
+class FakeSavingReactor(object):
+    """A fake reactor that saves connection attempts."""
+
+    def __init__(self):
+        """Initialize this fake instance."""
+        self.connections = []
+
+    def connectTCP(self, host, port, factory, *args):
+        """Fake the connection."""
+        self.connections.append((host, port, args))
+        factory.response_headers = {}
+        factory.deferred = defer.succeed("response content")
+
+    def connectSSL(self, host, port, factory, *args):
+        """Fake the connection."""
+        self.connections.append((host, port, args))
+        factory.response_headers = {}
+        factory.deferred = defer.succeed("response content")
+
+
+class TxWebClientTestCase(WebClientTestCase):
+    """Test case for txweb."""
+
+    webclient_factory = txweb.WebClient
+
+
+class TxWebClientReactorReplaceableTestCase(TestCase):
+    """In the txweb client the reactor is replaceable."""
+
+    timeout = 3
+    FAKE_HOST = u"fake"
+    FAKE_IRI_TEMPLATE = u"%%s://%s/fake_page" % FAKE_HOST
+
+    @defer.inlineCallbacks
+    def _test_replaceable_reactor(self, iri):
+        """The reactor can be replaced with the tunnel client."""
+        fake_reactor = FakeSavingReactor()
+        wc = txweb.WebClient(fake_reactor)
+        _response = yield wc.request(iri)
+        host, _port, _args = fake_reactor.connections[0]
+        self.assertEqual(host, self.FAKE_HOST)
+
+    def test_replaceable_reactor_http(self):
+        """Test the replaceable reactor with an http iri."""
+        return self._test_replaceable_reactor(self.FAKE_IRI_TEMPLATE % "http")
+
+    def test_replaceable_reactor_https(self):
+        """Test the replaceable reactor with an https iri."""
+        return self._test_replaceable_reactor(self.FAKE_IRI_TEMPLATE % "https")
+
+
 class TimestampCheckerTestCase(TestCase):
     """Tests for the timestampchecker classmethod."""
 
@@ -375,6 +452,8 @@
 class BasicProxyTestCase(SquidTestCase):
     """Test that the proxy works at all."""
 
+    timeout = 3
+
     @defer.inlineCallbacks
     def setUp(self):
         yield super(BasicProxyTestCase, self).setUp()
@@ -404,10 +483,119 @@
         result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
         self.assert_header_contains(result.headers["Via"], "squid")
 
+    @defer.inlineCallbacks
+    def test_auth_proxy_is_used_creds_requested(self):
+        """The authenticated proxy is used by the webclient."""
+        settings = self.get_auth_proxy_settings()
+        partial_settings = dict(host=settings['host'], port=settings['port'])
+
+        def fake_creds_request(domain, retry):
+            """Fake user interaction."""
+            self.wc.proxy_username = settings['username']
+            self.wc.proxy_password = settings['password']
+            return defer.succeed(True)
+
+        self.patch(self.wc, 'request_proxy_auth_credentials',
+                   fake_creds_request)
+
+        self.wc.force_use_proxy(partial_settings)
+        result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
+        self.assert_header_contains(result.headers["Via"], "squid")
+
+    @defer.inlineCallbacks
+    def test_auth_proxy_is_requested_creds_bad_details(self):
+        """Test using wrong credentials with the proxy."""
+        settings = self.get_auth_proxy_settings()
+        wrong_settings = dict(host=settings['host'], port=settings['port'],
+                              username=settings['password'],
+                              password=settings['username'])
+
+        def fake_creds_request(domain, retry):
+            """Fake user interaction."""
+            self.wc.proxy_username = settings['username']
+            self.wc.proxy_password = settings['password']
+            return defer.succeed(True)
+
+        self.patch(self.wc, 'request_proxy_auth_credentials',
+                   fake_creds_request)
+
+        self.wc.force_use_proxy(wrong_settings)
+        result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
+        self.assert_header_contains(result.headers["Via"], "squid")
+
+    @defer.inlineCallbacks
+    def test_auth_proxy_is_requested_creds_bad_details_user(self):
+        """Test using no creds and user providing the wrong ones."""
+        settings = self.get_auth_proxy_settings()
+        partial_settings = dict(host=settings['host'], port=settings['port'])
+
+        def fake_creds_request(domain, retry):
+            """Fake user interaction."""
+            if retry:
+                self.wc.proxy_username = settings['username']
+                self.wc.proxy_password = settings['password']
+            else:
+                self.wc.proxy_username = settings['password']
+                self.wc.proxy_password = settings['username']
+            return defer.succeed(True)
+
+        self.patch(self.wc, 'request_proxy_auth_credentials',
+                   fake_creds_request)
+
+        self.wc.force_use_proxy(partial_settings)
+        result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
+        self.assert_header_contains(result.headers["Via"], "squid")
+
+    @defer.inlineCallbacks
+    def test_auth_proxy_is_requested_creds_bad_details_everywhere(self):
+        """Test when we pass the wrong settings and get the wrong settings."""
+        settings = self.get_auth_proxy_settings()
+        wrong_settings = dict(host=settings['host'], port=settings['port'],
+                              username=settings['password'],
+                              password=settings['username'])
+
+        def fake_creds_request(domain, retry):
+            """Fake user interaction."""
+            if retry:
+                self.wc.proxy_username = settings['username']
+                self.wc.proxy_password = settings['password']
+            else:
+                self.wc.proxy_username = settings['password']
+                self.wc.proxy_password = settings['username']
+            return defer.succeed(True)
+
+        self.patch(self.wc, 'request_proxy_auth_credentials',
+                   fake_creds_request)
+
+        self.wc.force_use_proxy(wrong_settings)
+        result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
+        self.assert_header_contains(result.headers["Via"], "squid")
+
+    def test_auth_proxy_is_requested_user_cancels(self):
+        """Test when the user cancels the creds dialog."""
+        settings = self.get_auth_proxy_settings()
+        partial_settings = dict(host=settings['host'], port=settings['port'])
+
+        def fake_creds_request(domain, retry):
+            """Fake user interaction."""
+            return defer.succeed(False)
+
+        self.patch(self.wc, 'request_proxy_auth_credentials',
+                   fake_creds_request)
+
+        self.wc.force_use_proxy(partial_settings)
+        self.failUnlessFailure(self.wc.request(self.base_iri + SIMPLERESOURCE),
+                               webclient.WebClientError)
+
     if WEBCLIENT_MODULE_NAME.endswith(".txweb"):
         reason = "txweb does not support proxies."
         test_anonymous_proxy_is_used.skip = reason
-        test_authenticated_proxy_is_used.skip = reason
+        test_authenticated_proxy_is_used.kip = reason
+        test_auth_proxy_is_used_creds_requested.skip = reason
+        test_auth_proxy_is_requested_creds_bad_details.skip = reason
+        test_auth_proxy_is_requested_creds_bad_details_user.skip = reason
+        test_auth_proxy_is_requested_creds_bad_details_everywhere.skip = reason
+        test_auth_proxy_is_requested_user_cancels.skip = reason
 
 
 class HeaderDictTestCase(TestCase):
@@ -562,3 +750,315 @@
     """Test for the oauth signing code using HMAC-SHA1."""
 
     oauth_sign = "HMAC-SHA1"
+
+
+class FakeKeyring(object):
+    """A fake keyring."""
+
+    def __init__(self, creds):
+        """A fake keyring."""
+        self.creds = creds
+
+    def __call__(self):
+        """Fake instance callable."""
+        return self
+
+    def get_credentials(self, domain):
+        """A fake get_credentials."""
+        if isinstance(self.creds, Exception):
+            return defer.fail(self.creds)
+        return defer.succeed(self.creds)
+
+
+class RequestProxyAuthTestCase(TestCase):
+    """Test the spawn of the creds dialog."""
+
+    @defer.inlineCallbacks
+    def setUp(self):
+        """Set the different tests."""
+        yield super(RequestProxyAuthTestCase, self).setUp()
+        self.wc = webclient.webclient_factory()
+        self.addCleanup(self.wc.shutdown)
+        self.domain = 'domain'
+        self.retry = False
+        self.creds = dict(username='username', password='password')
+
+        self.keyring = FakeKeyring(self.creds)
+        self.patch(keyring, 'Keyring', self.keyring)
+
+        self.spawn_return_code = USER_SUCCESS
+
+        def fake_spawn_process(args):
+            """Fake spawning a process."""
+            if isinstance(self.spawn_return_code, Exception):
+                return defer.fail(self.spawn_return_code)
+            return defer.succeed(self.spawn_return_code)
+
+        self.patch(webclient.common, 'spawn_program', fake_spawn_process)
+
+    def test_spawn_error(self):
+        """Test the case when we cannot spawn the process."""
+        self.spawn_return_code = Exception()
+        self.failUnlessFailure(self.wc.request_proxy_auth_credentials(
+                                         self.domain, True),
+                                         webclient.WebClientError)
+
+    @defer.inlineCallbacks
+    def test_creds_acquired(self):
+        """Test the case in which we do get the creds."""
+        got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
+                                                           self.retry)
+        self.assertTrue(got_creds, 'Return true when creds are present.')
+        self.assertEqual(self.wc.proxy_username, self.creds['username'])
+        self.assertEqual(self.wc.proxy_password, self.creds['password'])
+
+    def test_creds_acquired_keyring_error(self):
+        """Test the case in which we cannot access the keyring."""
+        self.keyring.creds = Exception()
+        self.failUnlessFailure(self.wc.request_proxy_auth_credentials(
+                                         self.domain, self.retry),
+                                         webclient.WebClientError)
+
+    @defer.inlineCallbacks
+    def test_creds_none(self):
+        """Test the case in which we got None from the keyring."""
+        self.keyring.creds = None
+        got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
+                                                           self.retry)
+        self.assertFalse(got_creds, 'Return false when creds are not present.')
+
+    def test_user_cancelation(self):
+        """Test the case in which the user cancels."""
+        self.spawn_return_code = USER_CANCELLATION
+        got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
+                                                           self.retry)
+        self.assertFalse(got_creds, 'Return true when user cancels.')
+
+    def test_exception_error(self):
+        """Test the case in which something bad happened."""
+        self.spawn_return_code = EXCEPTION_RAISED
+        got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
+                                                           self.retry)
+        self.assertFalse(got_creds, 'Return true when user cancels.')
+
+
+class BaseSSLTestCase(SquidTestCase):
+    """Base test that allows to use ssl connections."""
+
+    @defer.inlineCallbacks
+    def setUp(self):
+        """Set the diff tests."""
+        yield super(BaseSSLTestCase, self).setUp()
+        self.cert_dir = os.path.join(self.tmpdir, 'cert')
+        self.cert_details = dict(organization='Canonical',
+                                 common_name=gethostname(),
+                                 locality_name='London',
+                                 unit='Ubuntu One',
+                                 country_name='UK',
+                                 state_name='London',)
+        self.ssl_settings = self._generate_self_signed_certificate(
+                                                             self.cert_dir,
+                                                             self.cert_details)
+        self.addCleanup(self._clean_ssl_certificate_files)
+
+        self.ws = MockWebServer(self.ssl_settings)
+        self.addCleanup(self.ws.stop)
+        self.base_iri = self.ws.get_iri()
+        self.base_ssl_iri = self.ws.get_ssl_iri()
+
+    def _clean_ssl_certificate_files(self):
+        """Remove the certificate files."""
+        if os.path.exists(self.cert_dir):
+            shutil.rmtree(self.cert_dir)
+
+    def _generate_self_signed_certificate(self, cert_dir, cert_details):
+        """Generate the required SSL certificates."""
+        if not os.path.exists(cert_dir):
+            os.makedirs(cert_dir)
+        cert_path = os.path.join(cert_dir, 'cert.crt')
+        key_path = os.path.join(cert_dir, 'cert.key')
+
+        if os.path.exists(cert_path):
+            os.unlink(cert_path)
+        if os.path.exists(key_path):
+            os.unlink(key_path)
+
+        # create a key pair
+        key = crypto.PKey()
+        key.generate_key(crypto.TYPE_RSA, 1024)
+
+        # create a self-signed cert
+        cert = crypto.X509()
+        cert.get_subject().C = cert_details['country_name']
+        cert.get_subject().ST = cert_details['state_name']
+        cert.get_subject().L = cert_details['locality_name']
+        cert.get_subject().O = cert_details['organization']
+        cert.get_subject().OU = cert_details['unit']
+        cert.get_subject().CN = cert_details['common_name']
+        cert.set_serial_number(1000)
+        cert.gmtime_adj_notBefore(0)
+        cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
+        cert.set_issuer(cert.get_subject())
+        cert.set_pubkey(key)
+        cert.sign(key, 'sha1')
+
+        with open(cert_path, 'wt') as fd:
+            fd.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
+
+        with open(key_path, 'wt') as fd:
+            fd.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
+
+        return dict(key=key_path, cert=cert_path)
+
+
+class CorrectProxyTestCase(BaseSSLTestCase):
+    """Test the interaction with a SSL enabled proxy."""
+
+    @defer.inlineCallbacks
+    def setUp(self):
+        """Set the tests."""
+        yield super(CorrectProxyTestCase, self).setUp()
+
+        # fake the gsettings to have diff settings for https and http
+        http_settings = self.get_auth_proxy_settings()
+
+        #remember so that we can use them in the creds request
+        proxy_username = http_settings['username']
+        proxy_password = http_settings['password']
+
+        # delete the username and password so that we get a 407 for testing
+        del http_settings['username']
+        del http_settings['password']
+
+        https_settings = self.get_nonauth_proxy_settings()
+
+        proxy_settings = dict(http=http_settings, https=https_settings)
+        self.patch(gsettings, "get_proxy_settings", lambda: proxy_settings)
+
+        self.wc = webclient.webclient_factory()
+        self.addCleanup(self.wc.shutdown)
+
+        self.called = []
+
+        def fake_creds_request(domain, retry):
+            """Fake user interaction."""
+            self.called.append('request_proxy_auth_credentials')
+            self.wc.proxy_username = proxy_username
+            self.wc.proxy_password = proxy_password
+            return defer.succeed(True)
+
+        self.patch(self.wc, 'request_proxy_auth_credentials',
+                   fake_creds_request)
+
+    def assert_header_contains(self, headers, expected):
+        """One of the headers matching key must contain a given value."""
+        self.assertTrue(any(expected in value for value in headers))
+
+    @defer.inlineCallbacks
+    def test_https_request(self):
+        """Test using the correct proxy for the ssl request.
+
+        In order to assert that the correct proxy is used we expect not to call
+        the auth dialog since we set the https proxy not to use the auth proxy
+        and to fail because we are reaching a https page with bad self-signed
+        certs.
+        """
+        # we fail due to the fake ssl cert
+        yield self.failUnlessFailure(self.wc.request(
+                                     self.base_ssl_iri + SIMPLERESOURCE),
+                                     webclient.WebClientError)
+        # https requests do not use the auth proxy therefore called should be
+        # empty. This asserts that we are using the correct settings for the
+        # request.
+        self.assertEqual([], self.called)
+
+    @defer.inlineCallbacks
+    def test_http_request(self):
+        """Test using the correct proxy for the plain request.
+
+        This tests does the opposite to the https tests. We did set the auth
+        proxy for the http request therefore we expect the proxy dialog to be
+        used and not to get an error since we are not visiting a https with bad
+        self-signed certs.
+        """
+        # we do not fail since we are not going to the https page
+        result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
+        self.assert_header_contains(result.headers["Via"], "squid")
+        # assert that we did go through the auth proxy
+        self.assertIn('request_proxy_auth_credentials', self.called)
+
+    if WEBCLIENT_MODULE_NAME.endswith(".txweb"):
+        reason = 'Multiple proxy settings is not supported.'
+        test_https_request.skip = reason
+        test_http_request.skip = reason
+
+    if WEBCLIENT_MODULE_NAME.endswith(".libsoup"):
+        reason = 'Hard to test since we need to fully mock gsettings.'
+        test_https_request.skip = reason
+        test_http_request.skip = reason
+
+    if WEBCLIENT_MODULE_NAME.endswith(".qtnetwork"):
+        reason = ('Updating proxy settings is not well support due to bug'
+                  ' QTBUG-14850: https://bugreports.qt-project.org/'
+                  'browse/QTBUG-14850')
+        test_https_request.skip = reason
+        test_http_request.skip = reason
+
+
+class SSLTestCase(BaseSSLTestCase):
+    """Test error handling when dealing with ssl."""
+
+    @defer.inlineCallbacks
+    def setUp(self):
+        """Set the diff tests."""
+        yield super(SSLTestCase, self).setUp()
+
+        self.wc = webclient.webclient_factory()
+        self.addCleanup(self.wc.shutdown)
+
+        self.return_code = USER_CANCELLATION
+        self.called = []
+
+        def fake_launch_ssl_dialog(client, domain, details):
+            """Fake the ssl dialog."""
+            self.called.append(('_launch_ssl_dialog', domain, details))
+            return defer.succeed(self.return_code)
+
+        self.patch(BaseWebClient, '_launch_ssl_dialog', fake_launch_ssl_dialog)
+
+    @defer.inlineCallbacks
+    def _assert_ssl_fail_user_accepts(self, proxy_settings=None):
+        """Assert the dialog is shown in an ssl fail."""
+        self.return_code = USER_SUCCESS
+        if proxy_settings:
+            self.wc.force_use_proxy(proxy_settings)
+        yield self.wc.request(self.base_ssl_iri + SIMPLERESOURCE)
+        details = SSL_DETAILS_TEMPLATE % self.cert_details
+        self.assertIn(('_launch_ssl_dialog', gethostname(), details),
+                      self.called)
+
+    def test_ssl_fail_dialog_user_accepts(self):
+        """Test showing the dialog and accepting."""
+        self._assert_ssl_fail_user_accepts()
+
+    def test_ssl_fail_dialog_user_accepts_via_proxy(self):
+        """Test showing the dialog and accepting when using a proxy."""
+        self._assert_ssl_fail_user_accepts(self.get_nonauth_proxy_settings())
+
+    def test_ssl_fail_dialog_user_rejects(self):
+        """Test showing the dialog and rejecting."""
+        self.failUnlessFailure(self.wc.request(self.base_iri + SIMPLERESOURCE),
+                                     webclient.WebClientError)
+
+    def test_format_ssl_details(self):
+        """Assert that details are correctly formatted"""
+        details = SSL_DETAILS_TEMPLATE % self.cert_details
+        self.assertEqual(details,
+                self.wc.format_ssl_details(self.cert_details))
+
+    if (WEBCLIENT_MODULE_NAME.endswith(".txweb") or
+            WEBCLIENT_MODULE_NAME.endswith(".libsoup")):
+        reason = 'SSL support has not yet been implemented.'
+        test_ssl_fail_dialog_user_accepts.skip = reason
+        test_ssl_fail_dialog_user_accepts_via_proxy.skip = reason
+        test_ssl_fail_dialog_user_rejects.skip = reason

=== modified file 'ubuntu_sso/utils/webclient/txweb.py'
--- ubuntu_sso/utils/webclient/txweb.py	2012-02-07 19:36:50 +0000
+++ ubuntu_sso/utils/webclient/txweb.py	2012-03-20 16:10:09 +0000
@@ -16,11 +16,9 @@
 """A webclient backend that uses twisted.web.client."""
 
 import base64
-
-from StringIO import StringIO
-
-from twisted.internet import defer, protocol
-from zope.interface import implements
+import urlparse
+
+from twisted.internet import defer
 
 from ubuntu_sso.utils.webclient.common import (
     BaseWebClient,
@@ -31,64 +29,80 @@
 )
 
 
-class StringProtocol(protocol.Protocol):
-    """Hold the stuff received in a StringIO."""
-
-    # pylint: disable=C0103
-    def __init__(self):
-        """Initialize this instance."""
-        self.deferred = defer.Deferred()
-        self.content = StringIO()
-
-    def dataReceived(self, data):
-        """Some more blocks received."""
-        self.content.write(data)
-
-    def connectionLost(self, reason=protocol.connectionDone):
-        """No more bytes available."""
-        self.deferred.callback(self.content.getvalue())
-
-
-class StringProducer(object):
-    """Simple implementation of IBodyProducer."""
-
-    # delay import, otherwise a default reactor gets installed
-    from twisted.web import iweb
-
-    implements(iweb.IBodyProducer)
-
-    def __init__(self, body):
-        """Initialize this instance with some bytes."""
-        self.body = body
-        self.length = len(body)
-
-    # pylint: disable=C0103
-    def startProducing(self, consumer):
-        """Start producing to the given IConsumer provider."""
-        consumer.write(self.body)
-        return defer.succeed(None)
-
-    def pauseProducing(self):
-        """In our case, do nothing."""
-
-    def stopProducing(self):
-        """In our case, do nothing."""
+class RawResponse(object):
+    """A raw response from the webcall."""
+
+    def __init__(self, headers, content, code=200, phrase="OK"):
+        """Initialize this response."""
+        self.headers = headers
+        self.content = content
+        self.code = code
+        self.phrase = phrase
 
 
 class WebClient(BaseWebClient):
     """A simple web client that does not support proxies, yet."""
 
-    # delay import, otherwise a default reactor gets installed
-    from twisted.internet import reactor
-    from twisted.web import client, http, http_headers
-
-    # Undefined variable 'http_headers', 'client', 'reactor', 'http'
-    # pylint: disable=E0602
+    def __init__(self, connector=None, context_factory=None, **kwargs):
+        """Initialize this webclient."""
+        super(WebClient, self).__init__(**kwargs)
+
+        if connector is None:
+            from twisted.internet import reactor
+            self.connector = reactor
+        else:
+            self.connector = connector
+
+        if context_factory is None:
+            from twisted.internet import ssl
+            self.context_factory = ssl.ClientContextFactory()
+        else:
+            self.context_factory = context_factory
+
+    @defer.inlineCallbacks
+    def raw_request(self, method, uri, headers, postdata):
+        """Make a raw http request."""
+        # delay import, otherwise a default reactor gets installed
+        from twisted.web import client, error
+
+        parsed_url = urlparse.urlparse(uri)
+
+        # pylint: disable=E1101
+        https = parsed_url.scheme == "https"
+        host = parsed_url.netloc.split(":")[0]
+        # pylint: enable=E1101
+        if parsed_url.port is None:
+            port = 443 if https else 80
+        else:
+            port = parsed_url.port
+
+        factory = client.HTTPClientFactory(uri, method=method,
+                                           postdata=postdata,
+                                           headers=headers,
+                                           followRedirect=False)
+        # pylint: disable=E1103
+        if https:
+            self.connector.connectSSL(host, port, factory,
+                                      self.context_factory)
+        else:
+            self.connector.connectTCP(host, port, factory)
+        # pylint: enable=E1103
+
+        try:
+            content = yield factory.deferred
+            response = RawResponse(factory.response_headers, content)
+        except error.Error as e:
+            response = RawResponse(factory.response_headers, e.response,
+                                   int(e.status), e.message)
+        defer.returnValue(response)
 
     @defer.inlineCallbacks
     def request(self, iri, method="GET", extra_headers=None,
                 oauth_credentials=None, post_content=None):
         """Get the page, or fail trying."""
+        # delay import, otherwise a default reactor gets installed
+        from twisted.web import http
+
         uri = self.iri_to_uri(iri)
 
         if extra_headers:
@@ -107,40 +121,25 @@
             headers["Authorization"] = "Basic " + auth
 
         try:
-            request_headers = http_headers.Headers()
-            for key, value in headers.items():
-                request_headers.addRawHeader(key, value)
-            agent = client.Agent(reactor)
-            if post_content:
-                body_producer = StringProducer(post_content)
-            else:
-                body_producer = None
-            agent_response = yield agent.request(method, uri,
-                                                 headers=request_headers,
-                                                 bodyProducer=body_producer)
-            raw_headers = agent_response.headers.getAllRawHeaders()
-            response_headers = HeaderDict(raw_headers)
+            raw_response = yield self.raw_request(method, uri,
+                                                  headers=headers,
+                                                  postdata=post_content)
+            response_headers = HeaderDict(raw_response.headers)
             if method.lower() != "head":
-                response_content = yield self.get_agent_content(agent_response)
+                response_content = raw_response.content
             else:
                 response_content = ""
-            if agent_response.code == http.OK:
+            if raw_response.code == http.OK:
                 defer.returnValue(Response(response_content, response_headers))
-            if agent_response.code == http.UNAUTHORIZED:
-                raise UnauthorizedError(agent_response.phrase,
+            if raw_response.code == http.UNAUTHORIZED:
+                raise UnauthorizedError(raw_response.phrase,
                                         response_content)
-            raise WebClientError(agent_response.phrase, response_content)
+            raise WebClientError(raw_response.phrase, response_content)
         except WebClientError:
             raise
         except Exception as e:
             raise WebClientError(e.message, e)
 
-    def get_agent_content(self, agent_response):
-        """Get the contents of an agent response."""
-        string_protocol = StringProtocol()
-        agent_response.deliverBody(string_protocol)
-        return string_protocol.deferred
-
     def force_use_proxy(self, settings):
         """Setup this webclient to use the given proxy settings."""
-        # No proxy support in twisted.web.client
+        # No direct proxy support in twisted.web.client

=== added file 'ubuntu_sso/utils/windows.py'
--- ubuntu_sso/utils/windows.py	1970-01-01 00:00:00 +0000
+++ ubuntu_sso/utils/windows.py	2012-03-20 16:10:09 +0000
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+#
+# 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
+# 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 <http://www.gnu.org/licenses/>.
+
+"""Platform specific constants and functions (for Windows)."""
+
+PLATFORM_QSS = ":/windows.qss"

