Home | History | Annotate | Download | only in login
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/login/new_user_view.h"
      6 
      7 #include <signal.h>
      8 #include <sys/types.h>
      9 
     10 #include <algorithm>
     11 #include <vector>
     12 
     13 #include "base/callback.h"
     14 #include "base/command_line.h"
     15 #include "base/message_loop.h"
     16 #include "base/process_util.h"
     17 #include "base/string_util.h"
     18 #include "base/utf_string_conversions.h"
     19 #include "chrome/browser/browser_process.h"
     20 #include "chrome/browser/chromeos/cros/cros_library.h"
     21 #include "chrome/browser/chromeos/login/rounded_rect_painter.h"
     22 #include "chrome/browser/chromeos/login/textfield_with_margin.h"
     23 #include "chrome/browser/chromeos/login/wizard_accessibility_helper.h"
     24 #include "chrome/browser/chromeos/user_cros_settings_provider.h"
     25 #include "chrome/browser/chromeos/views/copy_background.h"
     26 #include "chrome/browser/prefs/pref_service.h"
     27 #include "chrome/common/pref_names.h"
     28 #include "grit/app_resources.h"
     29 #include "grit/chromium_strings.h"
     30 #include "grit/generated_resources.h"
     31 #include "ui/base/keycodes/keyboard_codes.h"
     32 #include "ui/base/l10n/l10n_util.h"
     33 #include "ui/base/resource/resource_bundle.h"
     34 #include "ui/gfx/font.h"
     35 #include "views/controls/button/menu_button.h"
     36 #include "views/controls/label.h"
     37 #include "views/controls/textfield/textfield.h"
     38 #include "views/controls/throbber.h"
     39 #include "views/widget/widget_gtk.h"
     40 
     41 using views::View;
     42 
     43 namespace {
     44 
     45 const int kTextfieldWidth = 230;
     46 const int kSplitterHeight = 1;
     47 const int kTitlePad = 20;
     48 const int kRowPad = 13;
     49 const int kBottomPad = 33;
     50 const int kLeftPad = 33;
     51 const int kColumnPad = 7;
     52 const int kLanguagesMenuHeight = 25;
     53 const int kLanguagesMenuPad = 5;
     54 const SkColor kLanguagesMenuTextColor = 0xFF999999;
     55 const SkColor kErrorColor = 0xFF8F384F;
     56 const SkColor kSplitterUp1Color = 0xFFD0D2D3;
     57 const SkColor kSplitterUp2Color = 0xFFE1E3E4;
     58 const SkColor kSplitterDown1Color = 0xFFE3E6E8;
     59 const SkColor kSplitterDown2Color = 0xFFEAEDEE;
     60 const char kDefaultDomain[] = "@gmail.com";
     61 
     62 // Textfield that adds domain to the entered username if focus is lost and
     63 // username doesn't have full domain.
     64 class UsernameField : public chromeos::TextfieldWithMargin {
     65  public:
     66   explicit UsernameField(chromeos::NewUserView* controller)
     67       : controller_(controller) {}
     68 
     69   // views::Textfield overrides:
     70   virtual void OnBlur() OVERRIDE {
     71     string16 user_input;
     72     bool was_trim = TrimWhitespace(text(), TRIM_ALL, &user_input) != TRIM_NONE;
     73     if (!user_input.empty()) {
     74       std::string username = UTF16ToUTF8(user_input);
     75 
     76       if (username.find('@') == std::string::npos) {
     77         username += kDefaultDomain;
     78         SetText(UTF8ToUTF16(username));
     79         was_trim = false;
     80       }
     81     }
     82 
     83     if (was_trim)
     84       SetText(user_input);
     85   }
     86 
     87   // Overridden from views::View:
     88   virtual bool OnKeyPressed(const views::KeyEvent& e) OVERRIDE {
     89     if (e.key_code() == ui::VKEY_LEFT) {
     90       return controller_->NavigateAway();
     91     }
     92     return TextfieldWithMargin::OnKeyPressed(e);
     93   }
     94 
     95  private:
     96   chromeos::NewUserView* controller_;
     97   DISALLOW_COPY_AND_ASSIGN(UsernameField);
     98 };
     99 
    100 }  // namespace
    101 
    102 namespace chromeos {
    103 
    104 NewUserView::NewUserView(Delegate* delegate,
    105                          bool need_border,
    106                          bool need_guest_link)
    107     : username_field_(NULL),
    108       password_field_(NULL),
    109       title_label_(NULL),
    110       title_hint_label_(NULL),
    111       splitter_up1_(NULL),
    112       splitter_up2_(NULL),
    113       splitter_down1_(NULL),
    114       splitter_down2_(NULL),
    115       sign_in_button_(NULL),
    116       guest_link_(NULL),
    117       create_account_link_(NULL),
    118       languages_menubutton_(NULL),
    119       accel_focus_pass_(ui::VKEY_P, false, false, true),
    120       accel_focus_user_(ui::VKEY_U, false, false, true),
    121       accel_enterprise_enrollment_(ui::VKEY_E, false, true, true),
    122       accel_login_off_the_record_(ui::VKEY_B, false, false, true),
    123       accel_toggle_accessibility_(WizardAccessibilityHelper::GetAccelerator()),
    124       delegate_(delegate),
    125       ALLOW_THIS_IN_INITIALIZER_LIST(focus_grabber_factory_(this)),
    126       login_in_process_(false),
    127       need_border_(need_border),
    128       need_guest_link_(false),
    129       need_create_account_(false),
    130       languages_menubutton_order_(-1),
    131       sign_in_button_order_(-1) {
    132   if (UserCrosSettingsProvider::cached_allow_guest()) {
    133     need_create_account_ = true;
    134     if (need_guest_link)
    135       need_guest_link_ = true;
    136   }
    137 }
    138 
    139 NewUserView::~NewUserView() {
    140 }
    141 
    142 void NewUserView::Init() {
    143   if (need_border_) {
    144     // Use rounded rect background.
    145     set_border(CreateWizardBorder(&BorderDefinition::kUserBorder));
    146     views::Painter* painter = CreateWizardPainter(
    147         &BorderDefinition::kUserBorder);
    148     set_background(views::Background::CreateBackgroundPainter(true, painter));
    149   }
    150 
    151   title_label_ = new views::Label();
    152   title_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    153   title_label_->SetMultiLine(true);
    154   AddChildView(title_label_);
    155 
    156   title_hint_label_ = new views::Label();
    157   title_hint_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    158   title_hint_label_->SetColor(SK_ColorGRAY);
    159   title_hint_label_->SetMultiLine(true);
    160   AddChildView(title_hint_label_);
    161 
    162   splitter_up1_ = CreateSplitter(kSplitterUp1Color);
    163   splitter_up2_ = CreateSplitter(kSplitterUp2Color);
    164   splitter_down1_ = CreateSplitter(kSplitterDown1Color);
    165   splitter_down2_ = CreateSplitter(kSplitterDown2Color);
    166 
    167   username_field_ = new UsernameField(this);
    168   username_field_->set_background(new CopyBackground(this));
    169   username_field_->SetAccessibleName(
    170       l10n_util::GetStringUTF16(IDS_CHROMEOS_ACC_USERNAME_LABEL));
    171   AddChildView(username_field_);
    172 
    173   password_field_ = new TextfieldWithMargin(views::Textfield::STYLE_PASSWORD);
    174   password_field_->set_background(new CopyBackground(this));
    175   AddChildView(password_field_);
    176 
    177   language_switch_menu_.InitLanguageMenu();
    178 
    179   RecreatePeculiarControls();
    180 
    181   AddChildView(sign_in_button_);
    182   if (need_guest_link_) {
    183     InitLink(&guest_link_);
    184   }
    185   if (need_create_account_) {
    186     InitLink(&create_account_link_);
    187   }
    188   AddChildView(languages_menubutton_);
    189 
    190   // Set up accelerators.
    191   AddAccelerator(accel_focus_user_);
    192   AddAccelerator(accel_focus_pass_);
    193   AddAccelerator(accel_enterprise_enrollment_);
    194   AddAccelerator(accel_login_off_the_record_);
    195   AddAccelerator(accel_toggle_accessibility_);
    196 
    197   OnLocaleChanged();
    198 
    199   // Controller to handle events from textfields
    200   username_field_->SetController(this);
    201   password_field_->SetController(this);
    202   if (!CrosLibrary::Get()->EnsureLoaded()) {
    203     EnableInputControls(false);
    204   }
    205 
    206   // The 'Sign in' button should be disabled when there is no text in the
    207   // username and password fields.
    208   sign_in_button_->SetEnabled(false);
    209 }
    210 
    211 bool NewUserView::AcceleratorPressed(const views::Accelerator& accelerator) {
    212   if (accelerator == accel_focus_user_) {
    213     username_field_->RequestFocus();
    214   } else if (accelerator == accel_focus_pass_) {
    215     password_field_->RequestFocus();
    216   } else if (accelerator == accel_enterprise_enrollment_) {
    217     delegate_->OnStartEnterpriseEnrollment();
    218   } else if (accelerator == accel_login_off_the_record_) {
    219     delegate_->OnLoginAsGuest();
    220   } else if (accelerator == accel_toggle_accessibility_) {
    221     WizardAccessibilityHelper::GetInstance()->ToggleAccessibility();
    222   } else {
    223     return false;
    224   }
    225   return true;
    226 }
    227 
    228 void NewUserView::RecreatePeculiarControls() {
    229   // PreferredSize reported by MenuButton (and TextField) is not able
    230   // to shrink, only grow; so recreate on text change.
    231   delete languages_menubutton_;
    232   languages_menubutton_ = new views::MenuButton(
    233       NULL, std::wstring(), &language_switch_menu_, true);
    234   languages_menubutton_->set_menu_marker(
    235       ResourceBundle::GetSharedInstance().GetBitmapNamed(
    236           IDR_MENU_DROPARROW_SHARP));
    237   languages_menubutton_->SetEnabledColor(kLanguagesMenuTextColor);
    238   languages_menubutton_->SetFocusable(true);
    239   languages_menubutton_->SetEnabled(!g_browser_process->local_state()->
    240       IsManagedPreference(prefs::kApplicationLocale));
    241 
    242   // There is no way to get native button preferred size after the button was
    243   // sized so delete and recreate the button on text update.
    244   delete sign_in_button_;
    245   sign_in_button_ = new login::WideButton(this, std::wstring());
    246   UpdateSignInButtonState();
    247 
    248   if (!CrosLibrary::Get()->EnsureLoaded())
    249     sign_in_button_->SetEnabled(false);
    250 }
    251 
    252 void NewUserView::UpdateSignInButtonState() {
    253   bool enabled = !username_field_->text().empty() &&
    254                  !password_field_->text().empty();
    255   sign_in_button_->SetEnabled(enabled);
    256 }
    257 
    258 views::View* NewUserView::CreateSplitter(SkColor color) {
    259   views::View* splitter = new views::View();
    260   splitter->set_background(views::Background::CreateSolidBackground(color));
    261   AddChildView(splitter);
    262   return splitter;
    263 }
    264 
    265 void NewUserView::AddChildView(View* view) {
    266   // languages_menubutton_ and sign_in_button_ are recreated on text change,
    267   // so we restore their original position in layout.
    268   if (view == languages_menubutton_) {
    269     if (languages_menubutton_order_ < 0) {
    270       languages_menubutton_order_ = child_count();
    271     }
    272     views::View::AddChildViewAt(view, languages_menubutton_order_);
    273   } else if (view == sign_in_button_) {
    274     if (sign_in_button_order_ < 0) {
    275       sign_in_button_order_ = child_count();
    276     }
    277     views::View::AddChildViewAt(view, sign_in_button_order_);
    278   } else {
    279     views::View::AddChildView(view);
    280   }
    281 }
    282 
    283 void NewUserView::UpdateLocalizedStringsAndFonts() {
    284   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    285   gfx::Font title_font = rb.GetFont(ResourceBundle::MediumBoldFont).DeriveFont(
    286       kLoginTitleFontDelta);
    287   const gfx::Font& title_hint_font = rb.GetFont(ResourceBundle::BoldFont);
    288   const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont);
    289 
    290   title_label_->SetFont(title_font);
    291   title_label_->SetText(UTF16ToWide(
    292       l10n_util::GetStringUTF16(IDS_LOGIN_TITLE)));
    293   title_hint_label_->SetFont(title_hint_font);
    294   title_hint_label_->SetText(UTF16ToWide(
    295       l10n_util::GetStringUTF16(IDS_LOGIN_TITLE_HINT)));
    296   SetAndCorrectTextfieldFont(username_field_, base_font);
    297   username_field_->set_text_to_display_when_empty(
    298       l10n_util::GetStringUTF16(IDS_LOGIN_USERNAME));
    299   SetAndCorrectTextfieldFont(password_field_, base_font);
    300   password_field_->set_text_to_display_when_empty(
    301       l10n_util::GetStringUTF16(IDS_LOGIN_PASSWORD));
    302   sign_in_button_->SetLabel(UTF16ToWide(
    303       l10n_util::GetStringUTF16(IDS_LOGIN_BUTTON)));
    304   if (need_guest_link_) {
    305     guest_link_->SetFont(base_font);
    306     guest_link_->SetText(UTF16ToWide(
    307         l10n_util::GetStringUTF16(IDS_BROWSE_WITHOUT_SIGNING_IN_BUTTON)));
    308   }
    309   if (need_create_account_) {
    310     create_account_link_->SetFont(base_font);
    311     create_account_link_->SetText(
    312         UTF16ToWide(l10n_util::GetStringUTF16(IDS_CREATE_ACCOUNT_BUTTON)));
    313   }
    314   delegate_->ClearErrors();
    315   languages_menubutton_->SetText(
    316       UTF16ToWide(language_switch_menu_.GetCurrentLocaleName()));
    317 }
    318 
    319 void NewUserView::OnLocaleChanged() {
    320   RecreatePeculiarControls();
    321   UpdateLocalizedStringsAndFonts();
    322   AddChildView(sign_in_button_);
    323   AddChildView(languages_menubutton_);
    324 
    325   Layout();
    326   SchedulePaint();
    327   RequestFocus();
    328 }
    329 
    330 void NewUserView::RequestFocus() {
    331   if (username_field_->text().empty())
    332     username_field_->RequestFocus();
    333   else
    334     password_field_->RequestFocus();
    335 }
    336 
    337 void NewUserView::ViewHierarchyChanged(bool is_add,
    338                                        View *parent,
    339                                        View *child) {
    340   if (is_add && (child == username_field_ || child == password_field_)) {
    341     MessageLoop::current()->PostTask(FROM_HERE,
    342         focus_grabber_factory_.NewRunnableMethod(
    343             &NewUserView::Layout));
    344   }
    345 }
    346 
    347 // Sets the bounds of the view, using x and y as the origin.
    348 // The width is determined by the min of width and the preferred size
    349 // of the view, unless force_width is true in which case it is always used.
    350 // The height is gotten from the preferred size and returned.
    351 static int setViewBounds(
    352     views::View* view, int x, int y, int width, bool force_width) {
    353   gfx::Size pref_size = view->GetPreferredSize();
    354   if (!force_width) {
    355     if (pref_size.width() < width) {
    356       width = pref_size.width();
    357     }
    358   }
    359   int height = pref_size.height();
    360   view->SetBounds(x, y, width, height);
    361   return height;
    362 }
    363 
    364 void NewUserView::Layout() {
    365   gfx::Insets insets = GetInsets();
    366 
    367   // Place language selection in top right corner.
    368   int x = std::max(0,
    369       this->width() - insets.right() -
    370           languages_menubutton_->GetPreferredSize().width() - kColumnPad);
    371   int y = insets.top() + kLanguagesMenuPad;
    372   int width = std::min(this->width() - insets.width() - 2 * kColumnPad,
    373                        languages_menubutton_->GetPreferredSize().width());
    374   int height = kLanguagesMenuHeight;
    375   languages_menubutton_->SetBounds(x, y, width, height);
    376   y += height + kTitlePad;
    377 
    378   width = std::min(this->width() - insets.width() - 2 * kColumnPad,
    379                    kTextfieldWidth);
    380   x = insets.left() + kLeftPad;
    381   int max_width = this->width() - x - std::max(insets.right(), x);
    382   title_label_->SizeToFit(max_width);
    383   title_hint_label_->SizeToFit(max_width);
    384 
    385   // Top align title and title hint.
    386   y += setViewBounds(title_label_, x, y, max_width, false);
    387   y += setViewBounds(title_hint_label_, x, y, max_width, false);
    388   int title_end = y + kTitlePad;
    389 
    390   splitter_up1_->SetBounds(0, title_end, this->width(), kSplitterHeight);
    391   splitter_up2_->SetBounds(0, title_end + 1, this->width(), kSplitterHeight);
    392 
    393   // Bottom controls.
    394   int links_height = 0;
    395   if (need_create_account_)
    396     links_height += create_account_link_->GetPreferredSize().height();
    397   if (need_guest_link_)
    398     links_height += guest_link_->GetPreferredSize().height();
    399   if (need_create_account_ && need_guest_link_)
    400     links_height += kRowPad;
    401   y = this->height() - insets.bottom() - kBottomPad;
    402   if (links_height)
    403     y -= links_height + kBottomPad;
    404   int bottom_start = y;
    405 
    406   splitter_down1_->SetBounds(0, y, this->width(), kSplitterHeight);
    407   splitter_down2_->SetBounds(0, y + 1, this->width(), kSplitterHeight);
    408 
    409   y += kBottomPad;
    410   if (need_guest_link_) {
    411     y += setViewBounds(guest_link_,
    412                        x, y, max_width, false) + kRowPad;
    413   }
    414   if (need_create_account_) {
    415     y += setViewBounds(create_account_link_, x, y, max_width, false);
    416   }
    417 
    418   // Center main controls.
    419   height = username_field_->GetPreferredSize().height() +
    420            password_field_->GetPreferredSize().height() +
    421            sign_in_button_->GetPreferredSize().height() +
    422            2 * kRowPad;
    423   y = title_end + (bottom_start - title_end - height) / 2;
    424 
    425   y += setViewBounds(username_field_, x, y, width, true) + kRowPad;
    426   y += setViewBounds(password_field_, x, y, width, true) + kRowPad;
    427 
    428   int sign_in_button_width = sign_in_button_->GetPreferredSize().width();
    429   setViewBounds(sign_in_button_, x, y, sign_in_button_width,true);
    430 
    431   SchedulePaint();
    432 }
    433 
    434 gfx::Size NewUserView::GetPreferredSize() {
    435   return need_guest_link_ ?
    436       gfx::Size(kNewUserPodFullWidth, kNewUserPodFullHeight) :
    437       gfx::Size(kNewUserPodSmallWidth, kNewUserPodSmallHeight);
    438 }
    439 
    440 void NewUserView::SetUsername(const std::string& username) {
    441   username_field_->SetText(UTF8ToUTF16(username));
    442 }
    443 
    444 void NewUserView::SetPassword(const std::string& password) {
    445   password_field_->SetText(UTF8ToUTF16(password));
    446 }
    447 
    448 void NewUserView::Login() {
    449   if (login_in_process_ ||
    450       username_field_->text().empty() ||
    451       password_field_->text().empty()) {
    452     UpdateSignInButtonState();
    453     return;
    454   }
    455 
    456   login_in_process_ = true;
    457   std::string username = UTF16ToUTF8(username_field_->text());
    458   // todo(cmasone) Need to sanitize memory used to store password.
    459   std::string password = UTF16ToUTF8(password_field_->text());
    460 
    461   if (username.find('@') == std::string::npos) {
    462     username += kDefaultDomain;
    463     username_field_->SetText(UTF8ToUTF16(username));
    464   }
    465 
    466   delegate_->OnLogin(username, password);
    467 }
    468 
    469 // Sign in button causes a login attempt.
    470 void NewUserView::ButtonPressed(views::Button* sender,
    471                                 const views::Event& event) {
    472   DCHECK(sender == sign_in_button_);
    473   Login();
    474 }
    475 
    476 void NewUserView::LinkActivated(views::Link* source, int event_flags) {
    477   if (source == create_account_link_) {
    478     delegate_->OnCreateAccount();
    479   } else if (source == guest_link_) {
    480     delegate_->OnLoginAsGuest();
    481   }
    482 }
    483 
    484 void NewUserView::ClearAndFocusControls() {
    485   login_in_process_ = false;
    486   SetUsername(std::string());
    487   SetPassword(std::string());
    488   username_field_->RequestFocus();
    489   UpdateSignInButtonState();
    490 }
    491 
    492 void NewUserView::ClearAndFocusPassword() {
    493   login_in_process_ = false;
    494   SetPassword(std::string());
    495   password_field_->RequestFocus();
    496   UpdateSignInButtonState();
    497 }
    498 
    499 gfx::Rect NewUserView::GetMainInputScreenBounds() const {
    500   return GetUsernameBounds();
    501 }
    502 
    503 gfx::Rect NewUserView::CalculateThrobberBounds(views::Throbber* throbber) {
    504   DCHECK(password_field_);
    505   DCHECK(sign_in_button_);
    506 
    507   gfx::Size throbber_size = throbber->GetPreferredSize();
    508   int x = password_field_->x();
    509   x += password_field_->width() - throbber_size.width();
    510   int y = sign_in_button_->y();
    511   y += (sign_in_button_->height() - throbber_size.height()) / 2;
    512 
    513   return gfx::Rect(gfx::Point(x, y), throbber_size);
    514 }
    515 
    516 gfx::Rect NewUserView::GetPasswordBounds() const {
    517   return password_field_->GetScreenBounds();
    518 }
    519 
    520 gfx::Rect NewUserView::GetUsernameBounds() const {
    521   return username_field_->GetScreenBounds();
    522 }
    523 
    524 bool NewUserView::HandleKeyEvent(views::Textfield* sender,
    525                                  const views::KeyEvent& key_event) {
    526   if (!CrosLibrary::Get()->EnsureLoaded() || login_in_process_)
    527     return false;
    528 
    529   if (key_event.key_code() == ui::VKEY_RETURN) {
    530     if (!username_field_->text().empty() && !password_field_->text().empty())
    531       Login();
    532     // Return true so that processing ends
    533     return true;
    534   }
    535   delegate_->ClearErrors();
    536   // Return false so that processing does not end
    537   return false;
    538 }
    539 
    540 void NewUserView::ContentsChanged(views::Textfield* sender,
    541                                   const string16& new_contents) {
    542   UpdateSignInButtonState();
    543   if (!new_contents.empty())
    544     delegate_->ClearErrors();
    545 }
    546 
    547 void NewUserView::EnableInputControls(bool enabled) {
    548   languages_menubutton_->SetEnabled(enabled &&
    549       !g_browser_process->local_state()->IsManagedPreference(
    550           prefs::kApplicationLocale));
    551   username_field_->SetEnabled(enabled);
    552   password_field_->SetEnabled(enabled);
    553   if (need_guest_link_) {
    554     guest_link_->SetEnabled(enabled);
    555   }
    556   if (need_create_account_) {
    557     create_account_link_->SetEnabled(enabled);
    558   }
    559   UpdateSignInButtonState();
    560 }
    561 
    562 bool NewUserView::NavigateAway() {
    563   if (username_field_->text().empty() &&
    564       password_field_->text().empty()) {
    565     delegate_->NavigateAway();
    566     return true;
    567   } else {
    568     return false;
    569   }
    570 }
    571 
    572 void NewUserView::InitLink(views::Link** link) {
    573   *link = new views::Link(std::wstring());
    574   (*link)->SetController(this);
    575   (*link)->SetNormalColor(login::kLinkColor);
    576   (*link)->SetHighlightedColor(login::kLinkColor);
    577   AddChildView(*link);
    578 }
    579 
    580 }  // namespace chromeos
    581