1 // Copyright (c) 2012 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/ui/views/sync/one_click_signin_bubble_view.h" 6 7 #include "base/callback_helpers.h" 8 #include "base/logging.h" 9 #include "chrome/browser/ui/browser.h" 10 #include "chrome/browser/ui/sync/one_click_signin_helper.h" 11 #include "chrome/browser/ui/sync/one_click_signin_histogram.h" 12 #include "chrome/common/url_constants.h" 13 #include "components/google/core/browser/google_util.h" 14 #include "grit/chromium_strings.h" 15 #include "grit/generated_resources.h" 16 #include "grit/theme_resources.h" 17 #include "grit/ui_resources.h" 18 #include "ui/base/l10n/l10n_util.h" 19 #include "ui/base/resource/resource_bundle.h" 20 #include "ui/events/keycodes/keyboard_codes.h" 21 #include "ui/views/controls/button/image_button.h" 22 #include "ui/views/controls/button/label_button.h" 23 #include "ui/views/controls/image_view.h" 24 #include "ui/views/controls/label.h" 25 #include "ui/views/controls/link.h" 26 #include "ui/views/layout/grid_layout.h" 27 #include "ui/views/layout/layout_constants.h" 28 #include "ui/views/widget/widget.h" 29 30 // Minimum width of the the bubble. 31 const int kMinBubbleWidth = 310; 32 33 // Minimum width for the multi-line label. 34 const int kMinimumDialogLabelWidth = 400; 35 const int kMinimumLabelWidth = 240; 36 const int kDialogMargin = 16; 37 38 namespace { 39 40 // The column set constants that can be used in the InitContent() function 41 // to layout views. 42 enum OneClickSigninBubbleColumnTypes { 43 COLUMN_SET_FILL_ALIGN, 44 COLUMN_SET_CONTROLS, 45 COLUMN_SET_TITLE_BAR 46 }; 47 } // namespace 48 49 // static 50 OneClickSigninBubbleView* OneClickSigninBubbleView::bubble_view_ = NULL; 51 52 // static 53 void OneClickSigninBubbleView::ShowBubble( 54 BrowserWindow::OneClickSigninBubbleType type, 55 const base::string16& email, 56 const base::string16& error_message, 57 scoped_ptr<OneClickSigninBubbleDelegate> delegate, 58 views::View* anchor_view, 59 const BrowserWindow::StartSyncCallback& start_sync) { 60 if (IsShowing()) 61 return; 62 63 switch (type) { 64 case BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE: 65 bubble_view_ = new OneClickSigninBubbleView( 66 error_message, base::string16(), delegate.Pass(), 67 anchor_view, start_sync, false); 68 break; 69 case BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_MODAL_DIALOG: 70 bubble_view_ = new OneClickSigninBubbleView( 71 base::string16(), base::string16(), delegate.Pass(), 72 anchor_view, start_sync, true); 73 break; 74 case BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_SAML_MODAL_DIALOG: 75 bubble_view_ = new OneClickSigninBubbleView( 76 base::string16(), email, delegate.Pass(), 77 anchor_view, start_sync, true); 78 break; 79 } 80 81 views::BubbleDelegateView::CreateBubble(bubble_view_)->Show(); 82 } 83 84 // static 85 bool OneClickSigninBubbleView::IsShowing() { 86 return bubble_view_ != NULL; 87 } 88 89 // static 90 void OneClickSigninBubbleView::Hide() { 91 if (IsShowing()) 92 bubble_view_->GetWidget()->Close(); 93 } 94 95 OneClickSigninBubbleView::OneClickSigninBubbleView( 96 const base::string16& error_message, 97 const base::string16& email, 98 scoped_ptr<OneClickSigninBubbleDelegate> delegate, 99 views::View* anchor_view, 100 const BrowserWindow::StartSyncCallback& start_sync_callback, 101 bool is_sync_dialog) 102 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT), 103 delegate_(delegate.Pass()), 104 error_message_(error_message), 105 email_(email), 106 start_sync_callback_(start_sync_callback), 107 is_sync_dialog_(is_sync_dialog), 108 advanced_link_(NULL), 109 learn_more_link_(NULL), 110 ok_button_(NULL), 111 undo_button_(NULL), 112 close_button_(NULL), 113 clicked_learn_more_(false) { 114 if (is_sync_dialog_) { 115 DCHECK(!start_sync_callback_.is_null()); 116 set_arrow(views::BubbleBorder::NONE); 117 set_anchor_view_insets(gfx::Insets(0, 0, anchor_view->height() / 2, 0)); 118 set_close_on_deactivate(false); 119 } 120 int margin = is_sync_dialog_ ? kDialogMargin : views::kButtonVEdgeMarginNew; 121 set_margins(gfx::Insets(margin, margin, margin, margin)); 122 } 123 124 OneClickSigninBubbleView::~OneClickSigninBubbleView() { 125 } 126 127 ui::ModalType OneClickSigninBubbleView::GetModalType() const { 128 return is_sync_dialog_? ui::MODAL_TYPE_CHILD : ui::MODAL_TYPE_NONE; 129 } 130 131 void OneClickSigninBubbleView::Init() { 132 views::GridLayout* layout = new views::GridLayout(this); 133 SetLayoutManager(layout); 134 135 SetBorder(views::Border::CreateEmptyBorder(8, 8, 8, 8)); 136 137 // Column set for descriptive text and link. 138 views::ColumnSet* cs = layout->AddColumnSet(COLUMN_SET_FILL_ALIGN); 139 cs->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER, 1, 140 views::GridLayout::USE_PREF, 0, 0); 141 142 // Column set for buttons at bottom of bubble. 143 cs = layout->AddColumnSet(COLUMN_SET_CONTROLS); 144 cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0, 145 views::GridLayout::USE_PREF, 0, 0); 146 cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing); 147 cs->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0, 148 views::GridLayout::USE_PREF, 0, 0); 149 cs->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0, 150 views::GridLayout::USE_PREF, 0, 0); 151 152 is_sync_dialog_ ? InitDialogContent(layout) : InitBubbleContent(layout); 153 154 // Add controls at the bottom. 155 // Don't display the advanced link for the error bubble. 156 if (is_sync_dialog_ || error_message_.empty()) { 157 InitAdvancedLink(); 158 layout->StartRow(0, COLUMN_SET_CONTROLS); 159 layout->AddView(advanced_link_); 160 } 161 162 InitButtons(layout); 163 ok_button_->SetIsDefault(true); 164 165 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, 0)); 166 } 167 168 void OneClickSigninBubbleView::InitBubbleContent(views::GridLayout* layout) { 169 layout->set_minimum_size(gfx::Size(kMinBubbleWidth, 0)); 170 171 // If no error occurred, add title message. 172 if (error_message_.empty()) { 173 views::ColumnSet* cs = layout->AddColumnSet(COLUMN_SET_TITLE_BAR); 174 cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, 0, 175 views::GridLayout::USE_PREF, 0, 0); 176 { 177 layout->StartRow(0, COLUMN_SET_TITLE_BAR); 178 179 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 180 views::Label* label = new views::Label( 181 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_TITLE_NEW), 182 rb.GetFontList(ui::ResourceBundle::MediumFont)); 183 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 184 layout->AddView(label); 185 } 186 187 layout->AddPaddingRow(0, views::kUnrelatedControlLargeVerticalSpacing); 188 } 189 190 // Add main text description. 191 layout->StartRow(0, COLUMN_SET_FILL_ALIGN); 192 193 views::Label* label = !error_message_.empty() ? 194 new views::Label(error_message_) : 195 new views::Label( 196 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_BUBBLE_MESSAGE)); 197 198 label->SetMultiLine(true); 199 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 200 label->SizeToFit(kMinimumLabelWidth); 201 layout->AddView(label); 202 203 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); 204 205 layout->StartRow(0, COLUMN_SET_CONTROLS); 206 207 InitLearnMoreLink(); 208 layout->AddView(learn_more_link_); 209 } 210 211 void OneClickSigninBubbleView::InitDialogContent(views::GridLayout* layout) { 212 OneClickSigninHelper::LogConfirmHistogramValue( 213 one_click_signin::HISTOGRAM_CONFIRM_SHOWN); 214 215 // Column set for title bar. 216 views::ColumnSet* cs = layout->AddColumnSet(COLUMN_SET_TITLE_BAR); 217 cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0, 218 views::GridLayout::USE_PREF, 0, 0); 219 cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing); 220 cs->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0, 221 views::GridLayout::USE_PREF, 0, 0); 222 223 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 224 225 { 226 layout->StartRow(0, COLUMN_SET_TITLE_BAR); 227 228 views::Label* label = new views::Label( 229 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_TITLE_NEW), 230 rb.GetFontList(ui::ResourceBundle::MediumBoldFont)); 231 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 232 layout->AddView(label); 233 234 close_button_ = new views::ImageButton(this); 235 close_button_->SetImage(views::ImageButton::STATE_NORMAL, 236 rb.GetImageNamed(IDR_CLOSE_2).ToImageSkia()); 237 close_button_->SetImage(views::ImageButton::STATE_HOVERED, 238 rb.GetImageNamed(IDR_CLOSE_2_H).ToImageSkia()); 239 close_button_->SetImage(views::ImageButton::STATE_PRESSED, 240 rb.GetImageNamed(IDR_CLOSE_2_P).ToImageSkia()); 241 242 layout->AddView(close_button_); 243 } 244 245 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); 246 247 { 248 layout->StartRow(0, COLUMN_SET_FILL_ALIGN); 249 250 views::Label* label = new views::Label( 251 l10n_util::GetStringFUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_MESSAGE_NEW, 252 email_)); 253 label->SetMultiLine(true); 254 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 255 label->SizeToFit(kMinimumDialogLabelWidth); 256 layout->AddView(label); 257 258 layout->StartRow(0, COLUMN_SET_FILL_ALIGN); 259 260 InitLearnMoreLink(); 261 layout->AddView(learn_more_link_, 1, 1, views::GridLayout::TRAILING, 262 views::GridLayout::CENTER); 263 } 264 265 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); 266 } 267 268 void OneClickSigninBubbleView::InitButtons(views::GridLayout* layout) { 269 GetButtons(&ok_button_, &undo_button_); 270 layout->AddView(ok_button_); 271 272 if (is_sync_dialog_) 273 layout->AddView(undo_button_); 274 } 275 276 void OneClickSigninBubbleView::GetButtons(views::LabelButton** ok_button, 277 views::LabelButton** undo_button) { 278 base::string16 ok_label = !error_message_.empty() ? 279 l10n_util::GetStringUTF16(IDS_OK) : 280 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_OK_BUTTON); 281 282 *ok_button = new views::LabelButton(this, ok_label); 283 (*ok_button)->SetStyle(views::Button::STYLE_BUTTON); 284 285 // The default size of the buttons is too large. To allow them to be smaller 286 // ignore the minimum default size., 287 (*ok_button)->set_min_size(gfx::Size()); 288 289 if (is_sync_dialog_) { 290 *undo_button = new views::LabelButton(this, base::string16()); 291 (*undo_button)->SetStyle(views::Button::STYLE_BUTTON); 292 (*undo_button)->set_min_size(gfx::Size()); 293 294 base::string16 undo_label = 295 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_UNDO_BUTTON); 296 (*undo_button)->SetText(undo_label); 297 } 298 } 299 300 void OneClickSigninBubbleView::InitAdvancedLink() { 301 advanced_link_ = new views::Link( 302 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_ADVANCED)); 303 304 advanced_link_->set_listener(this); 305 advanced_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 306 } 307 308 void OneClickSigninBubbleView::InitLearnMoreLink() { 309 learn_more_link_ = new views::Link( 310 l10n_util::GetStringUTF16(IDS_LEARN_MORE)); 311 learn_more_link_->set_listener(this); 312 learn_more_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 313 } 314 315 bool OneClickSigninBubbleView::AcceleratorPressed( 316 const ui::Accelerator& accelerator) { 317 if (accelerator.key_code() == ui::VKEY_RETURN || 318 accelerator.key_code() == ui::VKEY_ESCAPE) { 319 OneClickSigninBubbleView::Hide(); 320 321 if (is_sync_dialog_) { 322 if (accelerator.key_code() == ui::VKEY_RETURN) { 323 OneClickSigninHelper::LogConfirmHistogramValue( 324 clicked_learn_more_ ? 325 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_RETURN : 326 one_click_signin::HISTOGRAM_CONFIRM_RETURN); 327 328 base::ResetAndReturn(&start_sync_callback_).Run( 329 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); 330 } else if (accelerator.key_code() == ui::VKEY_ESCAPE) { 331 OneClickSigninHelper::LogConfirmHistogramValue( 332 clicked_learn_more_ ? 333 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_ESCAPE : 334 one_click_signin::HISTOGRAM_CONFIRM_ESCAPE); 335 336 base::ResetAndReturn(&start_sync_callback_).Run( 337 OneClickSigninSyncStarter::UNDO_SYNC); 338 } 339 } 340 341 return true; 342 } 343 344 return BubbleDelegateView::AcceleratorPressed(accelerator); 345 } 346 347 void OneClickSigninBubbleView::LinkClicked(views::Link* source, 348 int event_flags) { 349 if (source == learn_more_link_) { 350 if (is_sync_dialog_ && !clicked_learn_more_) { 351 OneClickSigninHelper::LogConfirmHistogramValue( 352 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE); 353 clicked_learn_more_ = true; 354 } 355 delegate_->OnLearnMoreLinkClicked(is_sync_dialog_); 356 357 // don't hide the modal dialog, as this is an informational link 358 if (is_sync_dialog_) 359 return; 360 } else if (advanced_link_ && source == advanced_link_) { 361 if (is_sync_dialog_) { 362 OneClickSigninHelper::LogConfirmHistogramValue( 363 clicked_learn_more_ ? 364 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_ADVANCED : 365 one_click_signin::HISTOGRAM_CONFIRM_ADVANCED); 366 367 base::ResetAndReturn(&start_sync_callback_).Run( 368 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST); 369 } else { 370 delegate_->OnAdvancedLinkClicked(); 371 } 372 } 373 374 Hide(); 375 } 376 377 void OneClickSigninBubbleView::ButtonPressed(views::Button* sender, 378 const ui::Event& event) { 379 Hide(); 380 381 if (is_sync_dialog_) { 382 if (sender == ok_button_) 383 OneClickSigninHelper::LogConfirmHistogramValue( 384 clicked_learn_more_ ? 385 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_OK : 386 one_click_signin::HISTOGRAM_CONFIRM_OK); 387 388 if (sender == undo_button_) 389 OneClickSigninHelper::LogConfirmHistogramValue( 390 clicked_learn_more_ ? 391 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_UNDO : 392 one_click_signin::HISTOGRAM_CONFIRM_UNDO); 393 394 if (sender == close_button_) 395 OneClickSigninHelper::LogConfirmHistogramValue( 396 clicked_learn_more_ ? 397 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_CLOSE : 398 one_click_signin::HISTOGRAM_CONFIRM_CLOSE); 399 400 base::ResetAndReturn(&start_sync_callback_).Run((sender == ok_button_) ? 401 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS : 402 OneClickSigninSyncStarter::UNDO_SYNC); 403 } 404 } 405 406 void OneClickSigninBubbleView::WindowClosing() { 407 // We have to reset |bubble_view_| here, not in our destructor, because 408 // we'll be destroyed asynchronously and the shown state will be checked 409 // before then. 410 DCHECK_EQ(bubble_view_, this); 411 bubble_view_ = NULL; 412 413 if (is_sync_dialog_ && !start_sync_callback_.is_null()) { 414 base::ResetAndReturn(&start_sync_callback_).Run( 415 OneClickSigninSyncStarter::UNDO_SYNC); 416 } 417 } 418