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