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 ¶ms); 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 ¶ms); 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