// 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/user_controller.h" #include <algorithm> #include <vector> #include "base/utf_string_conversions.h" #include "chrome/browser/chromeos/login/existing_user_view.h" #include "chrome/browser/chromeos/login/guest_user_view.h" #include "chrome/browser/chromeos/login/helper.h" #include "chrome/browser/chromeos/login/rounded_rect_painter.h" #include "chrome/browser/chromeos/login/user_view.h" #include "chrome/browser/chromeos/login/username_view.h" #include "chrome/browser/chromeos/login/wizard_accessibility_helper.h" #include "chrome/browser/chromeos/login/wizard_controller.h" #include "chrome/browser/chromeos/user_cros_settings_provider.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "third_party/cros/chromeos_wm_ipc_enums.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/canvas.h" #include "views/background.h" #include "views/controls/button/native_button.h" #include "views/controls/label.h" #include "views/controls/throbber.h" #include "views/focus/focus_manager.h" #include "views/painter.h" #include "views/screen.h" #include "views/widget/root_view.h" #include "views/widget/widget_gtk.h" using views::Widget; using views::WidgetGtk; namespace chromeos { namespace { // Gap between the border around the image/buttons and user name. const int kUserNameGap = 4; // Approximate height of controls window, this constant is used in new user // case to make border window size close to existing users. #if defined(CROS_FONTS_USING_BCI) const int kControlsHeight = 31; #else const int kControlsHeight = 28; #endif // Vertical interval between the image and the textfield. const int kVerticalIntervalSize = 10; // A window for controls that sets focus to the view when // it first got focus. class ControlsWindow : public WidgetGtk { public: explicit ControlsWindow(views::View* initial_focus_view) : WidgetGtk(WidgetGtk::TYPE_WINDOW), initial_focus_view_(initial_focus_view) { } private: // WidgetGtk overrides: virtual void SetInitialFocus() OVERRIDE { if (initial_focus_view_) initial_focus_view_->RequestFocus(); } virtual void OnMap(GtkWidget* widget) OVERRIDE { // For some reason, Controls window never gets first expose event, // which makes WM believe that the login screen is not ready. // This is a workaround to let WM show the login screen. While // this may allow WM to show unpainted window, we haven't seen any // issue (yet). We will not investigate this further because we're // migrating to different implemention (WebUI). UpdateFreezeUpdatesProperty(GTK_WINDOW(GetNativeView()), false /* remove */); } views::View* initial_focus_view_; DISALLOW_COPY_AND_ASSIGN(ControlsWindow); }; // Widget that notifies window manager about clicking on itself. // Doesn't send anything if user is selected. class ClickNotifyingWidget : public views::WidgetGtk { public: ClickNotifyingWidget(views::WidgetGtk::Type type, UserController* controller) : WidgetGtk(type), controller_(controller) { } private: gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event) { if (!controller_->IsUserSelected()) controller_->SelectUserRelative(0); return views::WidgetGtk::OnButtonPress(widget, event); } UserController* controller_; DISALLOW_COPY_AND_ASSIGN(ClickNotifyingWidget); }; void CloseWindow(views::Widget* window) { if (!window) return; window->set_widget_delegate(NULL); window->Close(); } } // namespace using login::kBorderSize; using login::kUserImageSize; // static const int UserController::kPadding = 30; // Max size needed when an entry is not selected. const int UserController::kUnselectedSize = 100; const int UserController::kNewUserUnselectedSize = 42; //////////////////////////////////////////////////////////////////////////////// // UserController, public: UserController::UserController(Delegate* delegate, bool is_guest) : user_index_(-1), is_user_selected_(false), is_new_user_(!is_guest), is_guest_(is_guest), is_owner_(false), show_name_tooltip_(false), delegate_(delegate), controls_window_(NULL), image_window_(NULL), border_window_(NULL), label_window_(NULL), unselected_label_window_(NULL), user_view_(NULL), label_view_(NULL), unselected_label_view_(NULL), user_input_(NULL), throbber_host_(NULL) { } UserController::UserController(Delegate* delegate, const UserManager::User& user) : user_index_(-1), is_user_selected_(false), is_new_user_(false), is_guest_(false), // Empty 'cached_owner()' means that owner hasn't been cached yet, not // that owner has an empty email. is_owner_(user.email() == UserCrosSettingsProvider::cached_owner()), show_name_tooltip_(false), user_(user), delegate_(delegate), controls_window_(NULL), image_window_(NULL), border_window_(NULL), label_window_(NULL), unselected_label_window_(NULL), user_view_(NULL), label_view_(NULL), unselected_label_view_(NULL), user_input_(NULL), throbber_host_(NULL) { DCHECK(!user.email().empty()); } UserController::~UserController() { // Reset the widget delegate of every window to NULL, so the user // controller will not get notified about the active window change. // See also crosbug.com/7400. CloseWindow(controls_window_); CloseWindow(image_window_); CloseWindow(border_window_); CloseWindow(label_window_); CloseWindow(unselected_label_window_); } void UserController::Init(int index, int total_user_count, bool need_browse_without_signin) { int controls_height = 0; int controls_width = 0; controls_window_ = CreateControlsWindow(index, &controls_width, &controls_height, need_browse_without_signin); image_window_ = CreateImageWindow(index); CreateBorderWindow(index, total_user_count, controls_width, controls_height); label_window_ = CreateLabelWindow(index, WM_IPC_WINDOW_LOGIN_LABEL); unselected_label_window_ = CreateLabelWindow(index, WM_IPC_WINDOW_LOGIN_UNSELECTED_LABEL); } void UserController::ClearAndEnableFields() { user_input_->EnableInputControls(true); user_input_->ClearAndFocusControls(); StopThrobber(); } void UserController::ClearAndEnablePassword() { // Somehow focus manager thinks that textfield is still focused but the // textfield doesn't know that. So we clear focus for focus manager so it // sets focus on the textfield again. // TODO(avayvod): Fix the actual issue. views::FocusManager* focus_manager = controls_window_->GetFocusManager(); if (focus_manager) focus_manager->ClearFocus(); user_input_->EnableInputControls(true); user_input_->ClearAndFocusPassword(); StopThrobber(); } void UserController::EnableNameTooltip(bool enable) { name_tooltip_enabled_ = enable; std::wstring tooltip_text; if (enable) tooltip_text = GetNameTooltip(); if (user_view_) user_view_->SetTooltipText(tooltip_text); if (label_view_) label_view_->SetTooltipText(tooltip_text); if (unselected_label_view_) unselected_label_view_->SetTooltipText(tooltip_text); } gfx::Rect UserController::GetMainInputScreenBounds() const { return user_input_->GetMainInputScreenBounds(); } void UserController::OnUserImageChanged(UserManager::User* user) { if (user_.email() != user->email()) return; user_.set_image(user->image()); // Controller might exist without windows, // i.e. if user pod doesn't fit on the screen. if (user_view_) user_view_->SetImage(user_.image(), user_.image()); } void UserController::SelectUserRelative(int shift) { delegate_->SelectUser(user_index() + shift); } void UserController::StartThrobber() { throbber_host_->StartThrobber(); } void UserController::StopThrobber() { throbber_host_->StopThrobber(); } void UserController::UpdateUserCount(int index, int total_user_count) { user_index_ = index; std::vector<int> params; params.push_back(index); params.push_back(total_user_count); params.push_back(is_new_user_ ? kNewUserUnselectedSize : kUnselectedSize); params.push_back(kPadding); WmIpc::instance()->SetWindowType( border_window_->GetNativeView(), WM_IPC_WINDOW_LOGIN_BORDER, ¶ms); } std::string UserController::GetAccessibleUserLabel() { if (is_new_user_) return l10n_util::GetStringUTF8(IDS_ADD_USER); if (is_guest_) return l10n_util::GetStringUTF8(IDS_GUEST); return user_.email(); } //////////////////////////////////////////////////////////////////////////////// // UserController, WidgetDelegate implementation: // void UserController::OnWidgetActivated(bool active) { is_user_selected_ = active; if (active) { delegate_->OnUserSelected(this); user_view_->SetRemoveButtonVisible( !is_new_user_ && !is_guest_ && !is_owner_); } else { user_view_->SetRemoveButtonVisible(false); delegate_->ClearErrors(); } } //////////////////////////////////////////////////////////////////////////////// // UserController, NewUserView::Delegate implementation: // void UserController::OnLogin(const std::string& username, const std::string& password) { if (is_new_user_) user_.set_email(username); user_input_->EnableInputControls(false); StartThrobber(); delegate_->Login(this, UTF8ToUTF16(password)); } void UserController::OnCreateAccount() { user_input_->EnableInputControls(false); StartThrobber(); delegate_->CreateAccount(); } void UserController::OnStartEnterpriseEnrollment() { delegate_->StartEnterpriseEnrollment(); } void UserController::OnLoginAsGuest() { user_input_->EnableInputControls(false); StartThrobber(); delegate_->LoginAsGuest(); } void UserController::ClearErrors() { delegate_->ClearErrors(); } void UserController::NavigateAway() { SelectUserRelative(-1); } //////////////////////////////////////////////////////////////////////////////// // UserController, UserView::Delegate implementation: // void UserController::OnLocaleChanged() { // Update text tooltips on guest and new user pods. if (is_guest_ || is_new_user_) { if (name_tooltip_enabled_) EnableNameTooltip(name_tooltip_enabled_); } label_view_->SetFont(GetLabelFont()); unselected_label_view_->SetFont(GetUnselectedLabelFont()); } void UserController::OnRemoveUser() { delegate_->RemoveUser(this); } //////////////////////////////////////////////////////////////////////////////// // UserController, private: // void UserController::ConfigureLoginWindow(WidgetGtk* window, int index, const gfx::Rect& bounds, chromeos::WmIpcWindowType type, views::View* contents_view) { window->MakeTransparent(); window->Init(NULL, bounds); window->SetContentsView(contents_view); window->set_widget_delegate(this); std::vector<int> params; params.push_back(index); WmIpc::instance()->SetWindowType( window->GetNativeView(), type, ¶ms); GdkWindow* gdk_window = window->GetNativeView()->window; gdk_window_set_back_pixmap(gdk_window, NULL, false); window->Show(); } WidgetGtk* UserController::CreateControlsWindow( int index, int* width, int* height, bool need_browse_without_signin) { views::View* control_view; if (is_new_user_) { NewUserView* new_user_view = new NewUserView(this, true, need_browse_without_signin); new_user_view->Init(); control_view = new_user_view; user_input_ = new_user_view; throbber_host_ = new_user_view; } else if (is_guest_) { GuestUserView* guest_user_view = new GuestUserView(this); guest_user_view->RecreateFields(); control_view = guest_user_view; user_input_ = guest_user_view; throbber_host_ = guest_user_view; } else { ExistingUserView* existing_user_view = new ExistingUserView(this); existing_user_view->RecreateFields(); control_view = existing_user_view; user_input_ = existing_user_view; throbber_host_ = existing_user_view; } *height = kControlsHeight; *width = kUserImageSize; if (is_new_user_) { gfx::Size size = control_view->GetPreferredSize(); *width = size.width(); *height = size.height(); } WidgetGtk* window = new ControlsWindow(control_view); ConfigureLoginWindow(window, index, gfx::Rect(*width, *height), WM_IPC_WINDOW_LOGIN_CONTROLS, control_view); return window; } WidgetGtk* UserController::CreateImageWindow(int index) { user_view_ = new UserView(this, true, !is_new_user_); ResourceBundle& rb = ResourceBundle::GetSharedInstance(); if (is_guest_) { SkBitmap* image = rb.GetBitmapNamed(IDR_LOGIN_GUEST); user_view_->SetImage(*image, *image); } else if (is_new_user_) { SkBitmap* image = rb.GetBitmapNamed(IDR_LOGIN_ADD_USER); SkBitmap* image_hover = rb.GetBitmapNamed(IDR_LOGIN_ADD_USER_HOVER); user_view_->SetImage(*image, *image_hover); } else { user_view_->SetImage(user_.image(), user_.image()); } WidgetGtk* window = new ClickNotifyingWidget(WidgetGtk::TYPE_WINDOW, this); ConfigureLoginWindow(window, index, gfx::Rect(user_view_->GetPreferredSize()), WM_IPC_WINDOW_LOGIN_IMAGE, user_view_); return window; } void UserController::CreateBorderWindow(int index, int total_user_count, int controls_width, int controls_height) { // New user login controls window is much higher than existing user's controls // window so window manager will place the control instead of image window. // New user will have 0 size border. int width = controls_width; int height = controls_height; if (!is_new_user_) { width += kBorderSize * 2; height += 2 * kBorderSize + kUserImageSize + kVerticalIntervalSize; } Widget::CreateParams params(Widget::CreateParams::TYPE_WINDOW); params.transparent = true; border_window_ = Widget::CreateWidget(params); border_window_->Init(NULL, gfx::Rect(0, 0, width, height)); if (!is_new_user_) { views::View* background_view = new views::View(); views::Painter* painter = CreateWizardPainter( &BorderDefinition::kUserBorder); background_view->set_background( views::Background::CreateBackgroundPainter(true, painter)); border_window_->SetContentsView(background_view); } UpdateUserCount(index, total_user_count); GdkWindow* gdk_window = border_window_->GetNativeView()->window; gdk_window_set_back_pixmap(gdk_window, NULL, false); border_window_->Show(); } WidgetGtk* UserController::CreateLabelWindow(int index, WmIpcWindowType type) { std::wstring text; if (is_guest_) { text = std::wstring(); } else if (is_new_user_) { // Add user should have label only in activated state. // When new user is the only, label is not needed. if (type == WM_IPC_WINDOW_LOGIN_LABEL && index != 0) text = UTF16ToWide(l10n_util::GetStringUTF16(IDS_ADD_USER)); } else { text = UTF8ToWide(user_.GetDisplayName()); } views::Label* label = NULL; if (is_new_user_) { label = new views::Label(text); } else if (type == WM_IPC_WINDOW_LOGIN_LABEL) { label = UsernameView::CreateShapedUsernameView(text, false); } else { DCHECK(type == WM_IPC_WINDOW_LOGIN_UNSELECTED_LABEL); // TODO(altimofeev): switch to the rounded username view. label = UsernameView::CreateShapedUsernameView(text, true); } const gfx::Font& font = (type == WM_IPC_WINDOW_LOGIN_LABEL) ? GetLabelFont() : GetUnselectedLabelFont(); label->SetFont(font); label->SetColor(login::kTextColor); if (type == WM_IPC_WINDOW_LOGIN_LABEL) label_view_ = label; else unselected_label_view_ = label; int width = (type == WM_IPC_WINDOW_LOGIN_LABEL) ? kUserImageSize : kUnselectedSize; if (is_new_user_) { // Make label as small as possible to don't show tooltip. width = 0; } int height = (type == WM_IPC_WINDOW_LOGIN_LABEL) ? login::kSelectedLabelHeight : login::kUnselectedLabelHeight; WidgetGtk* window = new ClickNotifyingWidget(WidgetGtk::TYPE_WINDOW, this); ConfigureLoginWindow(window, index, gfx::Rect(0, 0, width, height), type, label); return window; } gfx::Font UserController::GetLabelFont() { ResourceBundle& rb = ResourceBundle::GetSharedInstance(); return rb.GetFont(ResourceBundle::MediumBoldFont).DeriveFont( kSelectedUsernameFontDelta); } gfx::Font UserController::GetUnselectedLabelFont() { ResourceBundle& rb = ResourceBundle::GetSharedInstance(); return rb.GetFont(ResourceBundle::BaseFont).DeriveFont( kUnselectedUsernameFontDelta, gfx::Font::BOLD); } std::wstring UserController::GetNameTooltip() const { if (is_new_user_) return UTF16ToWide(l10n_util::GetStringUTF16(IDS_ADD_USER)); else if (is_guest_) return UTF16ToWide(l10n_util::GetStringUTF16(IDS_GO_INCOGNITO_BUTTON)); else return UTF8ToWide(user_.GetNameTooltip()); } } // namespace chromeos