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/user_controller.h"
      6 
      7 #include <algorithm>
      8 #include <vector>
      9 
     10 #include "base/utf_string_conversions.h"
     11 #include "chrome/browser/chromeos/login/existing_user_view.h"
     12 #include "chrome/browser/chromeos/login/guest_user_view.h"
     13 #include "chrome/browser/chromeos/login/helper.h"
     14 #include "chrome/browser/chromeos/login/rounded_rect_painter.h"
     15 #include "chrome/browser/chromeos/login/user_view.h"
     16 #include "chrome/browser/chromeos/login/username_view.h"
     17 #include "chrome/browser/chromeos/login/wizard_accessibility_helper.h"
     18 #include "chrome/browser/chromeos/login/wizard_controller.h"
     19 #include "chrome/browser/chromeos/user_cros_settings_provider.h"
     20 #include "grit/generated_resources.h"
     21 #include "grit/theme_resources.h"
     22 #include "third_party/cros/chromeos_wm_ipc_enums.h"
     23 #include "ui/base/l10n/l10n_util.h"
     24 #include "ui/base/resource/resource_bundle.h"
     25 #include "ui/gfx/canvas.h"
     26 #include "views/background.h"
     27 #include "views/controls/button/native_button.h"
     28 #include "views/controls/label.h"
     29 #include "views/controls/throbber.h"
     30 #include "views/focus/focus_manager.h"
     31 #include "views/painter.h"
     32 #include "views/screen.h"
     33 #include "views/widget/root_view.h"
     34 #include "views/widget/widget_gtk.h"
     35 
     36 using views::Widget;
     37 using views::WidgetGtk;
     38 
     39 namespace chromeos {
     40 
     41 namespace {
     42 
     43 // Gap between the border around the image/buttons and user name.
     44 const int kUserNameGap = 4;
     45 
     46 // Approximate height of controls window, this constant is used in new user
     47 // case to make border window size close to existing users.
     48 #if defined(CROS_FONTS_USING_BCI)
     49 const int kControlsHeight = 31;
     50 #else
     51 const int kControlsHeight = 28;
     52 #endif
     53 
     54 // Vertical interval between the image and the textfield.
     55 const int kVerticalIntervalSize = 10;
     56 
     57 // A window for controls that sets focus to the view when
     58 // it first got focus.
     59 class ControlsWindow : public WidgetGtk {
     60  public:
     61   explicit ControlsWindow(views::View* initial_focus_view)
     62       : WidgetGtk(WidgetGtk::TYPE_WINDOW),
     63         initial_focus_view_(initial_focus_view) {
     64   }
     65 
     66  private:
     67   // WidgetGtk overrides:
     68   virtual void SetInitialFocus() OVERRIDE {
     69     if (initial_focus_view_)
     70       initial_focus_view_->RequestFocus();
     71   }
     72 
     73   virtual void OnMap(GtkWidget* widget) OVERRIDE {
     74     // For some reason, Controls window never gets first expose event,
     75     // which makes WM believe that the login screen is not ready.
     76     // This is a workaround to let WM show the login screen. While
     77     // this may allow WM to show unpainted window, we haven't seen any
     78     // issue (yet). We will not investigate this further because we're
     79     // migrating to different implemention (WebUI).
     80     UpdateFreezeUpdatesProperty(GTK_WINDOW(GetNativeView()),
     81                                 false /* remove */);
     82   }
     83 
     84   views::View* initial_focus_view_;
     85 
     86   DISALLOW_COPY_AND_ASSIGN(ControlsWindow);
     87 };
     88 
     89 // Widget that notifies window manager about clicking on itself.
     90 // Doesn't send anything if user is selected.
     91 class ClickNotifyingWidget : public views::WidgetGtk {
     92  public:
     93   ClickNotifyingWidget(views::WidgetGtk::Type type,
     94                        UserController* controller)
     95       : WidgetGtk(type),
     96         controller_(controller) {
     97   }
     98 
     99  private:
    100   gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event) {
    101     if (!controller_->IsUserSelected())
    102       controller_->SelectUserRelative(0);
    103 
    104     return views::WidgetGtk::OnButtonPress(widget, event);
    105   }
    106 
    107   UserController* controller_;
    108 
    109   DISALLOW_COPY_AND_ASSIGN(ClickNotifyingWidget);
    110 };
    111 
    112 void CloseWindow(views::Widget* window) {
    113   if (!window)
    114     return;
    115   window->set_widget_delegate(NULL);
    116   window->Close();
    117 }
    118 
    119 }  // namespace
    120 
    121 using login::kBorderSize;
    122 using login::kUserImageSize;
    123 
    124 // static
    125 const int UserController::kPadding = 30;
    126 
    127 // Max size needed when an entry is not selected.
    128 const int UserController::kUnselectedSize = 100;
    129 const int UserController::kNewUserUnselectedSize = 42;
    130 
    131 ////////////////////////////////////////////////////////////////////////////////
    132 // UserController, public:
    133 
    134 UserController::UserController(Delegate* delegate, bool is_guest)
    135     : user_index_(-1),
    136       is_user_selected_(false),
    137       is_new_user_(!is_guest),
    138       is_guest_(is_guest),
    139       is_owner_(false),
    140       show_name_tooltip_(false),
    141       delegate_(delegate),
    142       controls_window_(NULL),
    143       image_window_(NULL),
    144       border_window_(NULL),
    145       label_window_(NULL),
    146       unselected_label_window_(NULL),
    147       user_view_(NULL),
    148       label_view_(NULL),
    149       unselected_label_view_(NULL),
    150       user_input_(NULL),
    151       throbber_host_(NULL) {
    152 }
    153 
    154 UserController::UserController(Delegate* delegate,
    155                                const UserManager::User& user)
    156     : user_index_(-1),
    157       is_user_selected_(false),
    158       is_new_user_(false),
    159       is_guest_(false),
    160       // Empty 'cached_owner()' means that owner hasn't been cached yet, not
    161       // that owner has an empty email.
    162       is_owner_(user.email() == UserCrosSettingsProvider::cached_owner()),
    163       show_name_tooltip_(false),
    164       user_(user),
    165       delegate_(delegate),
    166       controls_window_(NULL),
    167       image_window_(NULL),
    168       border_window_(NULL),
    169       label_window_(NULL),
    170       unselected_label_window_(NULL),
    171       user_view_(NULL),
    172       label_view_(NULL),
    173       unselected_label_view_(NULL),
    174       user_input_(NULL),
    175       throbber_host_(NULL) {
    176   DCHECK(!user.email().empty());
    177 }
    178 
    179 UserController::~UserController() {
    180   // Reset the widget delegate of every window to NULL, so the user
    181   // controller will not get notified about the active window change.
    182   // See also crosbug.com/7400.
    183   CloseWindow(controls_window_);
    184   CloseWindow(image_window_);
    185   CloseWindow(border_window_);
    186   CloseWindow(label_window_);
    187   CloseWindow(unselected_label_window_);
    188 }
    189 
    190 void UserController::Init(int index,
    191                           int total_user_count,
    192                           bool need_browse_without_signin) {
    193   int controls_height = 0;
    194   int controls_width = 0;
    195   controls_window_ =
    196       CreateControlsWindow(index, &controls_width, &controls_height,
    197                            need_browse_without_signin);
    198   image_window_ = CreateImageWindow(index);
    199   CreateBorderWindow(index, total_user_count, controls_width, controls_height);
    200   label_window_ = CreateLabelWindow(index, WM_IPC_WINDOW_LOGIN_LABEL);
    201   unselected_label_window_ =
    202       CreateLabelWindow(index, WM_IPC_WINDOW_LOGIN_UNSELECTED_LABEL);
    203 }
    204 
    205 void UserController::ClearAndEnableFields() {
    206   user_input_->EnableInputControls(true);
    207   user_input_->ClearAndFocusControls();
    208   StopThrobber();
    209 }
    210 
    211 void UserController::ClearAndEnablePassword() {
    212   // Somehow focus manager thinks that textfield is still focused but the
    213   // textfield doesn't know that. So we clear focus for focus manager so it
    214   // sets focus on the textfield again.
    215   // TODO(avayvod): Fix the actual issue.
    216   views::FocusManager* focus_manager = controls_window_->GetFocusManager();
    217   if (focus_manager)
    218     focus_manager->ClearFocus();
    219   user_input_->EnableInputControls(true);
    220   user_input_->ClearAndFocusPassword();
    221   StopThrobber();
    222 }
    223 
    224 void UserController::EnableNameTooltip(bool enable) {
    225   name_tooltip_enabled_ = enable;
    226   std::wstring tooltip_text;
    227   if (enable)
    228     tooltip_text = GetNameTooltip();
    229 
    230   if (user_view_)
    231     user_view_->SetTooltipText(tooltip_text);
    232   if (label_view_)
    233     label_view_->SetTooltipText(tooltip_text);
    234   if (unselected_label_view_)
    235     unselected_label_view_->SetTooltipText(tooltip_text);
    236 }
    237 
    238 gfx::Rect UserController::GetMainInputScreenBounds() const {
    239   return user_input_->GetMainInputScreenBounds();
    240 }
    241 
    242 void UserController::OnUserImageChanged(UserManager::User* user) {
    243   if (user_.email() != user->email())
    244     return;
    245   user_.set_image(user->image());
    246   // Controller might exist without windows,
    247   // i.e. if user pod doesn't fit on the screen.
    248   if (user_view_)
    249     user_view_->SetImage(user_.image(), user_.image());
    250 }
    251 
    252 void UserController::SelectUserRelative(int shift) {
    253   delegate_->SelectUser(user_index() + shift);
    254 }
    255 
    256 void UserController::StartThrobber() {
    257   throbber_host_->StartThrobber();
    258 }
    259 
    260 void UserController::StopThrobber() {
    261   throbber_host_->StopThrobber();
    262 }
    263 
    264 void UserController::UpdateUserCount(int index, int total_user_count) {
    265   user_index_ = index;
    266   std::vector<int> params;
    267   params.push_back(index);
    268   params.push_back(total_user_count);
    269   params.push_back(is_new_user_ ? kNewUserUnselectedSize : kUnselectedSize);
    270   params.push_back(kPadding);
    271   WmIpc::instance()->SetWindowType(
    272       border_window_->GetNativeView(),
    273       WM_IPC_WINDOW_LOGIN_BORDER,
    274       &params);
    275 }
    276 
    277 std::string UserController::GetAccessibleUserLabel() {
    278   if (is_new_user_)
    279     return l10n_util::GetStringUTF8(IDS_ADD_USER);
    280   if (is_guest_)
    281     return l10n_util::GetStringUTF8(IDS_GUEST);
    282   return user_.email();
    283 }
    284 
    285 ////////////////////////////////////////////////////////////////////////////////
    286 // UserController, WidgetDelegate implementation:
    287 //
    288 void UserController::OnWidgetActivated(bool active) {
    289   is_user_selected_ = active;
    290   if (active) {
    291     delegate_->OnUserSelected(this);
    292     user_view_->SetRemoveButtonVisible(
    293         !is_new_user_ && !is_guest_ && !is_owner_);
    294   } else {
    295     user_view_->SetRemoveButtonVisible(false);
    296     delegate_->ClearErrors();
    297   }
    298 }
    299 
    300 ////////////////////////////////////////////////////////////////////////////////
    301 // UserController, NewUserView::Delegate implementation:
    302 //
    303 void UserController::OnLogin(const std::string& username,
    304                              const std::string& password) {
    305   if (is_new_user_)
    306     user_.set_email(username);
    307 
    308   user_input_->EnableInputControls(false);
    309   StartThrobber();
    310 
    311   delegate_->Login(this, UTF8ToUTF16(password));
    312 }
    313 
    314 void UserController::OnCreateAccount() {
    315   user_input_->EnableInputControls(false);
    316   StartThrobber();
    317 
    318   delegate_->CreateAccount();
    319 }
    320 
    321 void UserController::OnStartEnterpriseEnrollment() {
    322   delegate_->StartEnterpriseEnrollment();
    323 }
    324 
    325 void UserController::OnLoginAsGuest() {
    326   user_input_->EnableInputControls(false);
    327   StartThrobber();
    328 
    329   delegate_->LoginAsGuest();
    330 }
    331 
    332 void UserController::ClearErrors() {
    333   delegate_->ClearErrors();
    334 }
    335 
    336 void UserController::NavigateAway() {
    337   SelectUserRelative(-1);
    338 }
    339 
    340 ////////////////////////////////////////////////////////////////////////////////
    341 // UserController, UserView::Delegate implementation:
    342 //
    343 void UserController::OnLocaleChanged() {
    344   // Update text tooltips on guest and new user pods.
    345   if (is_guest_ || is_new_user_) {
    346     if (name_tooltip_enabled_)
    347       EnableNameTooltip(name_tooltip_enabled_);
    348   }
    349   label_view_->SetFont(GetLabelFont());
    350   unselected_label_view_->SetFont(GetUnselectedLabelFont());
    351 }
    352 
    353 void UserController::OnRemoveUser() {
    354   delegate_->RemoveUser(this);
    355 }
    356 
    357 ////////////////////////////////////////////////////////////////////////////////
    358 // UserController, private:
    359 //
    360 void UserController::ConfigureLoginWindow(WidgetGtk* window,
    361                                           int index,
    362                                           const gfx::Rect& bounds,
    363                                           chromeos::WmIpcWindowType type,
    364                                           views::View* contents_view) {
    365   window->MakeTransparent();
    366   window->Init(NULL, bounds);
    367   window->SetContentsView(contents_view);
    368   window->set_widget_delegate(this);
    369 
    370   std::vector<int> params;
    371   params.push_back(index);
    372   WmIpc::instance()->SetWindowType(
    373       window->GetNativeView(),
    374       type,
    375       &params);
    376 
    377   GdkWindow* gdk_window = window->GetNativeView()->window;
    378   gdk_window_set_back_pixmap(gdk_window, NULL, false);
    379 
    380   window->Show();
    381 }
    382 
    383 WidgetGtk* UserController::CreateControlsWindow(
    384     int index,
    385     int* width, int* height,
    386     bool need_browse_without_signin) {
    387   views::View* control_view;
    388   if (is_new_user_) {
    389     NewUserView* new_user_view =
    390         new NewUserView(this, true, need_browse_without_signin);
    391     new_user_view->Init();
    392     control_view = new_user_view;
    393     user_input_ = new_user_view;
    394     throbber_host_ = new_user_view;
    395   } else if (is_guest_) {
    396     GuestUserView* guest_user_view = new GuestUserView(this);
    397     guest_user_view->RecreateFields();
    398     control_view = guest_user_view;
    399     user_input_ = guest_user_view;
    400     throbber_host_ = guest_user_view;
    401   } else {
    402     ExistingUserView* existing_user_view = new ExistingUserView(this);
    403     existing_user_view->RecreateFields();
    404     control_view = existing_user_view;
    405     user_input_ = existing_user_view;
    406     throbber_host_ = existing_user_view;
    407   }
    408 
    409   *height = kControlsHeight;
    410   *width = kUserImageSize;
    411   if (is_new_user_) {
    412     gfx::Size size = control_view->GetPreferredSize();
    413     *width = size.width();
    414     *height = size.height();
    415   }
    416 
    417   WidgetGtk* window = new ControlsWindow(control_view);
    418   ConfigureLoginWindow(window,
    419                        index,
    420                        gfx::Rect(*width, *height),
    421                        WM_IPC_WINDOW_LOGIN_CONTROLS,
    422                        control_view);
    423   return window;
    424 }
    425 
    426 WidgetGtk* UserController::CreateImageWindow(int index) {
    427   user_view_ = new UserView(this, true, !is_new_user_);
    428 
    429   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    430   if (is_guest_) {
    431     SkBitmap* image = rb.GetBitmapNamed(IDR_LOGIN_GUEST);
    432     user_view_->SetImage(*image, *image);
    433   } else if (is_new_user_) {
    434     SkBitmap* image = rb.GetBitmapNamed(IDR_LOGIN_ADD_USER);
    435     SkBitmap* image_hover = rb.GetBitmapNamed(IDR_LOGIN_ADD_USER_HOVER);
    436     user_view_->SetImage(*image, *image_hover);
    437   } else {
    438     user_view_->SetImage(user_.image(), user_.image());
    439   }
    440 
    441   WidgetGtk* window = new ClickNotifyingWidget(WidgetGtk::TYPE_WINDOW, this);
    442   ConfigureLoginWindow(window,
    443                        index,
    444                        gfx::Rect(user_view_->GetPreferredSize()),
    445                        WM_IPC_WINDOW_LOGIN_IMAGE,
    446                        user_view_);
    447 
    448   return window;
    449 }
    450 
    451 void UserController::CreateBorderWindow(int index,
    452                                         int total_user_count,
    453                                         int controls_width,
    454                                         int controls_height) {
    455   // New user login controls window is much higher than existing user's controls
    456   // window so window manager will place the control instead of image window.
    457   // New user will have 0 size border.
    458   int width = controls_width;
    459   int height = controls_height;
    460   if (!is_new_user_) {
    461     width += kBorderSize * 2;
    462     height += 2 * kBorderSize + kUserImageSize + kVerticalIntervalSize;
    463   }
    464 
    465   Widget::CreateParams params(Widget::CreateParams::TYPE_WINDOW);
    466   params.transparent = true;
    467   border_window_ = Widget::CreateWidget(params);
    468   border_window_->Init(NULL, gfx::Rect(0, 0, width, height));
    469   if (!is_new_user_) {
    470     views::View* background_view = new views::View();
    471     views::Painter* painter = CreateWizardPainter(
    472         &BorderDefinition::kUserBorder);
    473     background_view->set_background(
    474         views::Background::CreateBackgroundPainter(true, painter));
    475     border_window_->SetContentsView(background_view);
    476   }
    477   UpdateUserCount(index, total_user_count);
    478 
    479   GdkWindow* gdk_window = border_window_->GetNativeView()->window;
    480   gdk_window_set_back_pixmap(gdk_window, NULL, false);
    481 
    482   border_window_->Show();
    483 }
    484 
    485 WidgetGtk* UserController::CreateLabelWindow(int index,
    486                                              WmIpcWindowType type) {
    487   std::wstring text;
    488   if (is_guest_) {
    489     text = std::wstring();
    490   } else if (is_new_user_) {
    491     // Add user should have label only in activated state.
    492     // When new user is the only, label is not needed.
    493     if (type == WM_IPC_WINDOW_LOGIN_LABEL && index != 0)
    494       text = UTF16ToWide(l10n_util::GetStringUTF16(IDS_ADD_USER));
    495   } else {
    496     text = UTF8ToWide(user_.GetDisplayName());
    497   }
    498 
    499   views::Label* label = NULL;
    500 
    501   if (is_new_user_) {
    502     label = new views::Label(text);
    503   } else if (type == WM_IPC_WINDOW_LOGIN_LABEL) {
    504     label = UsernameView::CreateShapedUsernameView(text, false);
    505   } else {
    506     DCHECK(type == WM_IPC_WINDOW_LOGIN_UNSELECTED_LABEL);
    507     // TODO(altimofeev): switch to the rounded username view.
    508     label = UsernameView::CreateShapedUsernameView(text, true);
    509   }
    510 
    511   const gfx::Font& font = (type == WM_IPC_WINDOW_LOGIN_LABEL) ?
    512       GetLabelFont() : GetUnselectedLabelFont();
    513   label->SetFont(font);
    514   label->SetColor(login::kTextColor);
    515 
    516   if (type == WM_IPC_WINDOW_LOGIN_LABEL)
    517     label_view_ = label;
    518   else
    519     unselected_label_view_ = label;
    520 
    521   int width = (type == WM_IPC_WINDOW_LOGIN_LABEL) ?
    522       kUserImageSize : kUnselectedSize;
    523   if (is_new_user_) {
    524     // Make label as small as possible to don't show tooltip.
    525     width = 0;
    526   }
    527   int height = (type == WM_IPC_WINDOW_LOGIN_LABEL) ?
    528       login::kSelectedLabelHeight : login::kUnselectedLabelHeight;
    529   WidgetGtk* window = new ClickNotifyingWidget(WidgetGtk::TYPE_WINDOW, this);
    530   ConfigureLoginWindow(window,
    531                        index,
    532                        gfx::Rect(0, 0, width, height),
    533                        type,
    534                        label);
    535   return window;
    536 }
    537 
    538 gfx::Font UserController::GetLabelFont() {
    539   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    540   return rb.GetFont(ResourceBundle::MediumBoldFont).DeriveFont(
    541       kSelectedUsernameFontDelta);
    542 }
    543 
    544 gfx::Font UserController::GetUnselectedLabelFont() {
    545   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    546   return rb.GetFont(ResourceBundle::BaseFont).DeriveFont(
    547       kUnselectedUsernameFontDelta, gfx::Font::BOLD);
    548 }
    549 
    550 
    551 std::wstring UserController::GetNameTooltip() const {
    552   if (is_new_user_)
    553     return UTF16ToWide(l10n_util::GetStringUTF16(IDS_ADD_USER));
    554   else if (is_guest_)
    555     return UTF16ToWide(l10n_util::GetStringUTF16(IDS_GO_INCOGNITO_BUTTON));
    556   else
    557     return UTF8ToWide(user_.GetNameTooltip());
    558 }
    559 
    560 }  // namespace chromeos
    561