普通文本  |  581行  |  18.82 KB

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/login/new_user_view.h"

#include <signal.h>
#include <sys/types.h>

#include <algorithm>
#include <vector>

#include "base/callback.h"
#include "base/command_line.h"
#include "base/message_loop.h"
#include "base/process_util.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/login/rounded_rect_painter.h"
#include "chrome/browser/chromeos/login/textfield_with_margin.h"
#include "chrome/browser/chromeos/login/wizard_accessibility_helper.h"
#include "chrome/browser/chromeos/user_cros_settings_provider.h"
#include "chrome/browser/chromeos/views/copy_background.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/common/pref_names.h"
#include "grit/app_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "ui/base/keycodes/keyboard_codes.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/font.h"
#include "views/controls/button/menu_button.h"
#include "views/controls/label.h"
#include "views/controls/textfield/textfield.h"
#include "views/controls/throbber.h"
#include "views/widget/widget_gtk.h"

using views::View;

namespace {

const int kTextfieldWidth = 230;
const int kSplitterHeight = 1;
const int kTitlePad = 20;
const int kRowPad = 13;
const int kBottomPad = 33;
const int kLeftPad = 33;
const int kColumnPad = 7;
const int kLanguagesMenuHeight = 25;
const int kLanguagesMenuPad = 5;
const SkColor kLanguagesMenuTextColor = 0xFF999999;
const SkColor kErrorColor = 0xFF8F384F;
const SkColor kSplitterUp1Color = 0xFFD0D2D3;
const SkColor kSplitterUp2Color = 0xFFE1E3E4;
const SkColor kSplitterDown1Color = 0xFFE3E6E8;
const SkColor kSplitterDown2Color = 0xFFEAEDEE;
const char kDefaultDomain[] = "@gmail.com";

// Textfield that adds domain to the entered username if focus is lost and
// username doesn't have full domain.
class UsernameField : public chromeos::TextfieldWithMargin {
 public:
  explicit UsernameField(chromeos::NewUserView* controller)
      : controller_(controller) {}

  // views::Textfield overrides:
  virtual void OnBlur() OVERRIDE {
    string16 user_input;
    bool was_trim = TrimWhitespace(text(), TRIM_ALL, &user_input) != TRIM_NONE;
    if (!user_input.empty()) {
      std::string username = UTF16ToUTF8(user_input);

      if (username.find('@') == std::string::npos) {
        username += kDefaultDomain;
        SetText(UTF8ToUTF16(username));
        was_trim = false;
      }
    }

    if (was_trim)
      SetText(user_input);
  }

  // Overridden from views::View:
  virtual bool OnKeyPressed(const views::KeyEvent& e) OVERRIDE {
    if (e.key_code() == ui::VKEY_LEFT) {
      return controller_->NavigateAway();
    }
    return TextfieldWithMargin::OnKeyPressed(e);
  }

 private:
  chromeos::NewUserView* controller_;
  DISALLOW_COPY_AND_ASSIGN(UsernameField);
};

}  // namespace

namespace chromeos {

NewUserView::NewUserView(Delegate* delegate,
                         bool need_border,
                         bool need_guest_link)
    : username_field_(NULL),
      password_field_(NULL),
      title_label_(NULL),
      title_hint_label_(NULL),
      splitter_up1_(NULL),
      splitter_up2_(NULL),
      splitter_down1_(NULL),
      splitter_down2_(NULL),
      sign_in_button_(NULL),
      guest_link_(NULL),
      create_account_link_(NULL),
      languages_menubutton_(NULL),
      accel_focus_pass_(ui::VKEY_P, false, false, true),
      accel_focus_user_(ui::VKEY_U, false, false, true),
      accel_enterprise_enrollment_(ui::VKEY_E, false, true, true),
      accel_login_off_the_record_(ui::VKEY_B, false, false, true),
      accel_toggle_accessibility_(WizardAccessibilityHelper::GetAccelerator()),
      delegate_(delegate),
      ALLOW_THIS_IN_INITIALIZER_LIST(focus_grabber_factory_(this)),
      login_in_process_(false),
      need_border_(need_border),
      need_guest_link_(false),
      need_create_account_(false),
      languages_menubutton_order_(-1),
      sign_in_button_order_(-1) {
  if (UserCrosSettingsProvider::cached_allow_guest()) {
    need_create_account_ = true;
    if (need_guest_link)
      need_guest_link_ = true;
  }
}

NewUserView::~NewUserView() {
}

void NewUserView::Init() {
  if (need_border_) {
    // Use rounded rect background.
    set_border(CreateWizardBorder(&BorderDefinition::kUserBorder));
    views::Painter* painter = CreateWizardPainter(
        &BorderDefinition::kUserBorder);
    set_background(views::Background::CreateBackgroundPainter(true, painter));
  }

  title_label_ = new views::Label();
  title_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  title_label_->SetMultiLine(true);
  AddChildView(title_label_);

  title_hint_label_ = new views::Label();
  title_hint_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  title_hint_label_->SetColor(SK_ColorGRAY);
  title_hint_label_->SetMultiLine(true);
  AddChildView(title_hint_label_);

  splitter_up1_ = CreateSplitter(kSplitterUp1Color);
  splitter_up2_ = CreateSplitter(kSplitterUp2Color);
  splitter_down1_ = CreateSplitter(kSplitterDown1Color);
  splitter_down2_ = CreateSplitter(kSplitterDown2Color);

  username_field_ = new UsernameField(this);
  username_field_->set_background(new CopyBackground(this));
  username_field_->SetAccessibleName(
      l10n_util::GetStringUTF16(IDS_CHROMEOS_ACC_USERNAME_LABEL));
  AddChildView(username_field_);

  password_field_ = new TextfieldWithMargin(views::Textfield::STYLE_PASSWORD);
  password_field_->set_background(new CopyBackground(this));
  AddChildView(password_field_);

  language_switch_menu_.InitLanguageMenu();

  RecreatePeculiarControls();

  AddChildView(sign_in_button_);
  if (need_guest_link_) {
    InitLink(&guest_link_);
  }
  if (need_create_account_) {
    InitLink(&create_account_link_);
  }
  AddChildView(languages_menubutton_);

  // Set up accelerators.
  AddAccelerator(accel_focus_user_);
  AddAccelerator(accel_focus_pass_);
  AddAccelerator(accel_enterprise_enrollment_);
  AddAccelerator(accel_login_off_the_record_);
  AddAccelerator(accel_toggle_accessibility_);

  OnLocaleChanged();

  // Controller to handle events from textfields
  username_field_->SetController(this);
  password_field_->SetController(this);
  if (!CrosLibrary::Get()->EnsureLoaded()) {
    EnableInputControls(false);
  }

  // The 'Sign in' button should be disabled when there is no text in the
  // username and password fields.
  sign_in_button_->SetEnabled(false);
}

bool NewUserView::AcceleratorPressed(const views::Accelerator& accelerator) {
  if (accelerator == accel_focus_user_) {
    username_field_->RequestFocus();
  } else if (accelerator == accel_focus_pass_) {
    password_field_->RequestFocus();
  } else if (accelerator == accel_enterprise_enrollment_) {
    delegate_->OnStartEnterpriseEnrollment();
  } else if (accelerator == accel_login_off_the_record_) {
    delegate_->OnLoginAsGuest();
  } else if (accelerator == accel_toggle_accessibility_) {
    WizardAccessibilityHelper::GetInstance()->ToggleAccessibility();
  } else {
    return false;
  }
  return true;
}

void NewUserView::RecreatePeculiarControls() {
  // PreferredSize reported by MenuButton (and TextField) is not able
  // to shrink, only grow; so recreate on text change.
  delete languages_menubutton_;
  languages_menubutton_ = new views::MenuButton(
      NULL, std::wstring(), &language_switch_menu_, true);
  languages_menubutton_->set_menu_marker(
      ResourceBundle::GetSharedInstance().GetBitmapNamed(
          IDR_MENU_DROPARROW_SHARP));
  languages_menubutton_->SetEnabledColor(kLanguagesMenuTextColor);
  languages_menubutton_->SetFocusable(true);
  languages_menubutton_->SetEnabled(!g_browser_process->local_state()->
      IsManagedPreference(prefs::kApplicationLocale));

  // There is no way to get native button preferred size after the button was
  // sized so delete and recreate the button on text update.
  delete sign_in_button_;
  sign_in_button_ = new login::WideButton(this, std::wstring());
  UpdateSignInButtonState();

  if (!CrosLibrary::Get()->EnsureLoaded())
    sign_in_button_->SetEnabled(false);
}

void NewUserView::UpdateSignInButtonState() {
  bool enabled = !username_field_->text().empty() &&
                 !password_field_->text().empty();
  sign_in_button_->SetEnabled(enabled);
}

views::View* NewUserView::CreateSplitter(SkColor color) {
  views::View* splitter = new views::View();
  splitter->set_background(views::Background::CreateSolidBackground(color));
  AddChildView(splitter);
  return splitter;
}

void NewUserView::AddChildView(View* view) {
  // languages_menubutton_ and sign_in_button_ are recreated on text change,
  // so we restore their original position in layout.
  if (view == languages_menubutton_) {
    if (languages_menubutton_order_ < 0) {
      languages_menubutton_order_ = child_count();
    }
    views::View::AddChildViewAt(view, languages_menubutton_order_);
  } else if (view == sign_in_button_) {
    if (sign_in_button_order_ < 0) {
      sign_in_button_order_ = child_count();
    }
    views::View::AddChildViewAt(view, sign_in_button_order_);
  } else {
    views::View::AddChildView(view);
  }
}

void NewUserView::UpdateLocalizedStringsAndFonts() {
  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
  gfx::Font title_font = rb.GetFont(ResourceBundle::MediumBoldFont).DeriveFont(
      kLoginTitleFontDelta);
  const gfx::Font& title_hint_font = rb.GetFont(ResourceBundle::BoldFont);
  const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont);

  title_label_->SetFont(title_font);
  title_label_->SetText(UTF16ToWide(
      l10n_util::GetStringUTF16(IDS_LOGIN_TITLE)));
  title_hint_label_->SetFont(title_hint_font);
  title_hint_label_->SetText(UTF16ToWide(
      l10n_util::GetStringUTF16(IDS_LOGIN_TITLE_HINT)));
  SetAndCorrectTextfieldFont(username_field_, base_font);
  username_field_->set_text_to_display_when_empty(
      l10n_util::GetStringUTF16(IDS_LOGIN_USERNAME));
  SetAndCorrectTextfieldFont(password_field_, base_font);
  password_field_->set_text_to_display_when_empty(
      l10n_util::GetStringUTF16(IDS_LOGIN_PASSWORD));
  sign_in_button_->SetLabel(UTF16ToWide(
      l10n_util::GetStringUTF16(IDS_LOGIN_BUTTON)));
  if (need_guest_link_) {
    guest_link_->SetFont(base_font);
    guest_link_->SetText(UTF16ToWide(
        l10n_util::GetStringUTF16(IDS_BROWSE_WITHOUT_SIGNING_IN_BUTTON)));
  }
  if (need_create_account_) {
    create_account_link_->SetFont(base_font);
    create_account_link_->SetText(
        UTF16ToWide(l10n_util::GetStringUTF16(IDS_CREATE_ACCOUNT_BUTTON)));
  }
  delegate_->ClearErrors();
  languages_menubutton_->SetText(
      UTF16ToWide(language_switch_menu_.GetCurrentLocaleName()));
}

void NewUserView::OnLocaleChanged() {
  RecreatePeculiarControls();
  UpdateLocalizedStringsAndFonts();
  AddChildView(sign_in_button_);
  AddChildView(languages_menubutton_);

  Layout();
  SchedulePaint();
  RequestFocus();
}

void NewUserView::RequestFocus() {
  if (username_field_->text().empty())
    username_field_->RequestFocus();
  else
    password_field_->RequestFocus();
}

void NewUserView::ViewHierarchyChanged(bool is_add,
                                       View *parent,
                                       View *child) {
  if (is_add && (child == username_field_ || child == password_field_)) {
    MessageLoop::current()->PostTask(FROM_HERE,
        focus_grabber_factory_.NewRunnableMethod(
            &NewUserView::Layout));
  }
}

// Sets the bounds of the view, using x and y as the origin.
// The width is determined by the min of width and the preferred size
// of the view, unless force_width is true in which case it is always used.
// The height is gotten from the preferred size and returned.
static int setViewBounds(
    views::View* view, int x, int y, int width, bool force_width) {
  gfx::Size pref_size = view->GetPreferredSize();
  if (!force_width) {
    if (pref_size.width() < width) {
      width = pref_size.width();
    }
  }
  int height = pref_size.height();
  view->SetBounds(x, y, width, height);
  return height;
}

void NewUserView::Layout() {
  gfx::Insets insets = GetInsets();

  // Place language selection in top right corner.
  int x = std::max(0,
      this->width() - insets.right() -
          languages_menubutton_->GetPreferredSize().width() - kColumnPad);
  int y = insets.top() + kLanguagesMenuPad;
  int width = std::min(this->width() - insets.width() - 2 * kColumnPad,
                       languages_menubutton_->GetPreferredSize().width());
  int height = kLanguagesMenuHeight;
  languages_menubutton_->SetBounds(x, y, width, height);
  y += height + kTitlePad;

  width = std::min(this->width() - insets.width() - 2 * kColumnPad,
                   kTextfieldWidth);
  x = insets.left() + kLeftPad;
  int max_width = this->width() - x - std::max(insets.right(), x);
  title_label_->SizeToFit(max_width);
  title_hint_label_->SizeToFit(max_width);

  // Top align title and title hint.
  y += setViewBounds(title_label_, x, y, max_width, false);
  y += setViewBounds(title_hint_label_, x, y, max_width, false);
  int title_end = y + kTitlePad;

  splitter_up1_->SetBounds(0, title_end, this->width(), kSplitterHeight);
  splitter_up2_->SetBounds(0, title_end + 1, this->width(), kSplitterHeight);

  // Bottom controls.
  int links_height = 0;
  if (need_create_account_)
    links_height += create_account_link_->GetPreferredSize().height();
  if (need_guest_link_)
    links_height += guest_link_->GetPreferredSize().height();
  if (need_create_account_ && need_guest_link_)
    links_height += kRowPad;
  y = this->height() - insets.bottom() - kBottomPad;
  if (links_height)
    y -= links_height + kBottomPad;
  int bottom_start = y;

  splitter_down1_->SetBounds(0, y, this->width(), kSplitterHeight);
  splitter_down2_->SetBounds(0, y + 1, this->width(), kSplitterHeight);

  y += kBottomPad;
  if (need_guest_link_) {
    y += setViewBounds(guest_link_,
                       x, y, max_width, false) + kRowPad;
  }
  if (need_create_account_) {
    y += setViewBounds(create_account_link_, x, y, max_width, false);
  }

  // Center main controls.
  height = username_field_->GetPreferredSize().height() +
           password_field_->GetPreferredSize().height() +
           sign_in_button_->GetPreferredSize().height() +
           2 * kRowPad;
  y = title_end + (bottom_start - title_end - height) / 2;

  y += setViewBounds(username_field_, x, y, width, true) + kRowPad;
  y += setViewBounds(password_field_, x, y, width, true) + kRowPad;

  int sign_in_button_width = sign_in_button_->GetPreferredSize().width();
  setViewBounds(sign_in_button_, x, y, sign_in_button_width,true);

  SchedulePaint();
}

gfx::Size NewUserView::GetPreferredSize() {
  return need_guest_link_ ?
      gfx::Size(kNewUserPodFullWidth, kNewUserPodFullHeight) :
      gfx::Size(kNewUserPodSmallWidth, kNewUserPodSmallHeight);
}

void NewUserView::SetUsername(const std::string& username) {
  username_field_->SetText(UTF8ToUTF16(username));
}

void NewUserView::SetPassword(const std::string& password) {
  password_field_->SetText(UTF8ToUTF16(password));
}

void NewUserView::Login() {
  if (login_in_process_ ||
      username_field_->text().empty() ||
      password_field_->text().empty()) {
    UpdateSignInButtonState();
    return;
  }

  login_in_process_ = true;
  std::string username = UTF16ToUTF8(username_field_->text());
  // todo(cmasone) Need to sanitize memory used to store password.
  std::string password = UTF16ToUTF8(password_field_->text());

  if (username.find('@') == std::string::npos) {
    username += kDefaultDomain;
    username_field_->SetText(UTF8ToUTF16(username));
  }

  delegate_->OnLogin(username, password);
}

// Sign in button causes a login attempt.
void NewUserView::ButtonPressed(views::Button* sender,
                                const views::Event& event) {
  DCHECK(sender == sign_in_button_);
  Login();
}

void NewUserView::LinkActivated(views::Link* source, int event_flags) {
  if (source == create_account_link_) {
    delegate_->OnCreateAccount();
  } else if (source == guest_link_) {
    delegate_->OnLoginAsGuest();
  }
}

void NewUserView::ClearAndFocusControls() {
  login_in_process_ = false;
  SetUsername(std::string());
  SetPassword(std::string());
  username_field_->RequestFocus();
  UpdateSignInButtonState();
}

void NewUserView::ClearAndFocusPassword() {
  login_in_process_ = false;
  SetPassword(std::string());
  password_field_->RequestFocus();
  UpdateSignInButtonState();
}

gfx::Rect NewUserView::GetMainInputScreenBounds() const {
  return GetUsernameBounds();
}

gfx::Rect NewUserView::CalculateThrobberBounds(views::Throbber* throbber) {
  DCHECK(password_field_);
  DCHECK(sign_in_button_);

  gfx::Size throbber_size = throbber->GetPreferredSize();
  int x = password_field_->x();
  x += password_field_->width() - throbber_size.width();
  int y = sign_in_button_->y();
  y += (sign_in_button_->height() - throbber_size.height()) / 2;

  return gfx::Rect(gfx::Point(x, y), throbber_size);
}

gfx::Rect NewUserView::GetPasswordBounds() const {
  return password_field_->GetScreenBounds();
}

gfx::Rect NewUserView::GetUsernameBounds() const {
  return username_field_->GetScreenBounds();
}

bool NewUserView::HandleKeyEvent(views::Textfield* sender,
                                 const views::KeyEvent& key_event) {
  if (!CrosLibrary::Get()->EnsureLoaded() || login_in_process_)
    return false;

  if (key_event.key_code() == ui::VKEY_RETURN) {
    if (!username_field_->text().empty() && !password_field_->text().empty())
      Login();
    // Return true so that processing ends
    return true;
  }
  delegate_->ClearErrors();
  // Return false so that processing does not end
  return false;
}

void NewUserView::ContentsChanged(views::Textfield* sender,
                                  const string16& new_contents) {
  UpdateSignInButtonState();
  if (!new_contents.empty())
    delegate_->ClearErrors();
}

void NewUserView::EnableInputControls(bool enabled) {
  languages_menubutton_->SetEnabled(enabled &&
      !g_browser_process->local_state()->IsManagedPreference(
          prefs::kApplicationLocale));
  username_field_->SetEnabled(enabled);
  password_field_->SetEnabled(enabled);
  if (need_guest_link_) {
    guest_link_->SetEnabled(enabled);
  }
  if (need_create_account_) {
    create_account_link_->SetEnabled(enabled);
  }
  UpdateSignInButtonState();
}

bool NewUserView::NavigateAway() {
  if (username_field_->text().empty() &&
      password_field_->text().empty()) {
    delegate_->NavigateAway();
    return true;
  } else {
    return false;
  }
}

void NewUserView::InitLink(views::Link** link) {
  *link = new views::Link(std::wstring());
  (*link)->SetController(this);
  (*link)->SetNormalColor(login::kLinkColor);
  (*link)->SetHighlightedColor(login::kLinkColor);
  AddChildView(*link);
}

}  // namespace chromeos