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.
5 #include "chrome/browser/ui/views/sync/one_click_signin_bubble_view.h"
7 #include "base/callback_helpers.h"
8 #include "base/logging.h"
9 #include "base/message_loop/message_loop.h"
10 #include "chrome/browser/google/google_util.h"
11 #include "chrome/browser/ui/browser.h"
12 #include "chrome/browser/ui/sync/one_click_signin_helper.h"
13 #include "chrome/browser/ui/sync/one_click_signin_histogram.h"
14 #include "chrome/common/url_constants.h"
15 #include "grit/chromium_strings.h"
16 #include "grit/generated_resources.h"
17 #include "grit/theme_resources.h"
18 #include "grit/ui_resources.h"
19 #include "ui/base/l10n/l10n_util.h"
20 #include "ui/base/resource/resource_bundle.h"
21 #include "ui/events/keycodes/keyboard_codes.h"
22 #include "ui/views/controls/button/image_button.h"
23 #include "ui/views/controls/button/label_button.h"
24 #include "ui/views/controls/image_view.h"
25 #include "ui/views/controls/label.h"
26 #include "ui/views/controls/link.h"
27 #include "ui/views/layout/grid_layout.h"
28 #include "ui/views/layout/layout_constants.h"
29 #include "ui/views/widget/widget.h"
31 // Minimum width of the the bubble.
32 const int kMinBubbleWidth = 310;
34 // Minimum width for the multi-line label.
35 const int kMinimumDialogLabelWidth = 400;
36 const int kMinimumLabelWidth = 240;
37 const int kDialogMargin = 16;
41 // The column set constants that can be used in the InitContent() function
43 enum OneClickSigninBubbleColumnTypes {
44 COLUMN_SET_FILL_ALIGN,
51 OneClickSigninBubbleView* OneClickSigninBubbleView::bubble_view_ = NULL;
54 void OneClickSigninBubbleView::ShowBubble(
55 BrowserWindow::OneClickSigninBubbleType type,
56 const base::string16& email,
57 const base::string16& error_message,
58 scoped_ptr<OneClickSigninBubbleDelegate> delegate,
59 views::View* anchor_view,
60 const BrowserWindow::StartSyncCallback& start_sync) {
65 case BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE:
66 bubble_view_ = new OneClickSigninBubbleView(
67 error_message, base::string16(), delegate.Pass(),
68 anchor_view, start_sync, false);
70 case BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_MODAL_DIALOG:
71 bubble_view_ = new OneClickSigninBubbleView(
72 base::string16(), base::string16(), delegate.Pass(),
73 anchor_view, start_sync, true);
75 case BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_SAML_MODAL_DIALOG:
76 bubble_view_ = new OneClickSigninBubbleView(
77 base::string16(), email, delegate.Pass(),
78 anchor_view, start_sync, true);
82 views::BubbleDelegateView::CreateBubble(bubble_view_)->Show();
86 bool OneClickSigninBubbleView::IsShowing() {
87 return bubble_view_ != NULL;
91 void OneClickSigninBubbleView::Hide() {
93 bubble_view_->GetWidget()->Close();
96 OneClickSigninBubbleView::OneClickSigninBubbleView(
97 const base::string16& error_message,
98 const base::string16& email,
99 scoped_ptr<OneClickSigninBubbleDelegate> delegate,
100 views::View* anchor_view,
101 const BrowserWindow::StartSyncCallback& start_sync_callback,
103 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
104 delegate_(delegate.Pass()),
105 error_message_(error_message),
107 start_sync_callback_(start_sync_callback),
108 is_sync_dialog_(is_sync_dialog),
109 advanced_link_(NULL),
110 learn_more_link_(NULL),
114 clicked_learn_more_(false),
115 message_loop_for_testing_(NULL) {
116 if (is_sync_dialog_) {
117 DCHECK(!start_sync_callback_.is_null());
118 set_arrow(views::BubbleBorder::NONE);
119 set_anchor_view_insets(gfx::Insets(0, 0, anchor_view->height() / 2, 0));
120 set_close_on_deactivate(false);
122 int margin = is_sync_dialog_ ? kDialogMargin : views::kButtonVEdgeMarginNew;
123 set_margins(gfx::Insets(margin, margin, margin, margin));
126 OneClickSigninBubbleView::~OneClickSigninBubbleView() {
129 ui::ModalType OneClickSigninBubbleView::GetModalType() const {
130 return is_sync_dialog_? ui::MODAL_TYPE_CHILD : ui::MODAL_TYPE_NONE;
133 void OneClickSigninBubbleView::AnimationEnded(const gfx::Animation* animation) {
134 views::BubbleDelegateView::AnimationEnded(animation);
135 if (message_loop_for_testing_)
136 message_loop_for_testing_->Quit();
139 void OneClickSigninBubbleView::Init() {
140 views::GridLayout* layout = new views::GridLayout(this);
141 SetLayoutManager(layout);
143 SetBorder(views::Border::CreateEmptyBorder(8, 8, 8, 8));
145 // Column set for descriptive text and link.
146 views::ColumnSet* cs = layout->AddColumnSet(COLUMN_SET_FILL_ALIGN);
147 cs->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER, 1,
148 views::GridLayout::USE_PREF, 0, 0);
150 // Column set for buttons at bottom of bubble.
151 cs = layout->AddColumnSet(COLUMN_SET_CONTROLS);
152 cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0,
153 views::GridLayout::USE_PREF, 0, 0);
154 cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing);
155 cs->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0,
156 views::GridLayout::USE_PREF, 0, 0);
157 cs->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0,
158 views::GridLayout::USE_PREF, 0, 0);
160 is_sync_dialog_ ? InitDialogContent(layout) : InitBubbleContent(layout);
162 // Add controls at the bottom.
163 // Don't display the advanced link for the error bubble.
164 if (is_sync_dialog_ || error_message_.empty()) {
166 layout->StartRow(0, COLUMN_SET_CONTROLS);
167 layout->AddView(advanced_link_);
171 ok_button_->SetIsDefault(true);
173 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, 0));
176 void OneClickSigninBubbleView::InitBubbleContent(views::GridLayout* layout) {
177 layout->set_minimum_size(gfx::Size(kMinBubbleWidth, 0));
179 // If no error occurred, add title message.
180 if (error_message_.empty()) {
181 views::ColumnSet* cs = layout->AddColumnSet(COLUMN_SET_TITLE_BAR);
182 cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, 0,
183 views::GridLayout::USE_PREF, 0, 0);
185 layout->StartRow(0, COLUMN_SET_TITLE_BAR);
187 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
188 views::Label* label = new views::Label(
189 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_TITLE_NEW),
190 rb.GetFontList(ui::ResourceBundle::MediumFont));
191 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
192 layout->AddView(label);
195 layout->AddPaddingRow(0, views::kUnrelatedControlLargeVerticalSpacing);
198 // Add main text description.
199 layout->StartRow(0, COLUMN_SET_FILL_ALIGN);
201 views::Label* label = !error_message_.empty() ?
202 new views::Label(error_message_) :
204 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_BUBBLE_MESSAGE));
206 label->SetMultiLine(true);
207 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
208 label->SizeToFit(kMinimumLabelWidth);
209 layout->AddView(label);
211 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
213 layout->StartRow(0, COLUMN_SET_CONTROLS);
216 layout->AddView(learn_more_link_);
219 void OneClickSigninBubbleView::InitDialogContent(views::GridLayout* layout) {
220 OneClickSigninHelper::LogConfirmHistogramValue(
221 one_click_signin::HISTOGRAM_CONFIRM_SHOWN);
223 // Column set for title bar.
224 views::ColumnSet* cs = layout->AddColumnSet(COLUMN_SET_TITLE_BAR);
225 cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0,
226 views::GridLayout::USE_PREF, 0, 0);
227 cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing);
228 cs->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0,
229 views::GridLayout::USE_PREF, 0, 0);
231 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
234 layout->StartRow(0, COLUMN_SET_TITLE_BAR);
236 views::Label* label = new views::Label(
237 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_TITLE_NEW),
238 rb.GetFontList(ui::ResourceBundle::MediumBoldFont));
239 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
240 layout->AddView(label);
242 close_button_ = new views::ImageButton(this);
243 close_button_->SetImage(views::ImageButton::STATE_NORMAL,
244 rb.GetImageNamed(IDR_CLOSE_2).ToImageSkia());
245 close_button_->SetImage(views::ImageButton::STATE_HOVERED,
246 rb.GetImageNamed(IDR_CLOSE_2_H).ToImageSkia());
247 close_button_->SetImage(views::ImageButton::STATE_PRESSED,
248 rb.GetImageNamed(IDR_CLOSE_2_P).ToImageSkia());
250 layout->AddView(close_button_);
253 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
256 layout->StartRow(0, COLUMN_SET_FILL_ALIGN);
258 views::Label* label = new views::Label(
259 l10n_util::GetStringFUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_MESSAGE_NEW,
261 label->SetMultiLine(true);
262 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
263 label->SizeToFit(kMinimumDialogLabelWidth);
264 layout->AddView(label);
266 layout->StartRow(0, COLUMN_SET_FILL_ALIGN);
269 layout->AddView(learn_more_link_, 1, 1, views::GridLayout::TRAILING,
270 views::GridLayout::CENTER);
273 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
276 void OneClickSigninBubbleView::InitButtons(views::GridLayout* layout) {
277 GetButtons(&ok_button_, &undo_button_);
278 layout->AddView(ok_button_);
281 layout->AddView(undo_button_);
284 void OneClickSigninBubbleView::GetButtons(views::LabelButton** ok_button,
285 views::LabelButton** undo_button) {
286 base::string16 ok_label = !error_message_.empty() ?
287 l10n_util::GetStringUTF16(IDS_OK) :
288 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_OK_BUTTON);
290 *ok_button = new views::LabelButton(this, ok_label);
291 (*ok_button)->SetStyle(views::Button::STYLE_BUTTON);
293 // The default size of the buttons is too large. To allow them to be smaller
294 // ignore the minimum default size.,
295 (*ok_button)->set_min_size(gfx::Size());
297 if (is_sync_dialog_) {
298 *undo_button = new views::LabelButton(this, base::string16());
299 (*undo_button)->SetStyle(views::Button::STYLE_BUTTON);
300 (*undo_button)->set_min_size(gfx::Size());
302 base::string16 undo_label =
303 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_UNDO_BUTTON);
304 (*undo_button)->SetText(undo_label);
308 void OneClickSigninBubbleView::InitAdvancedLink() {
309 advanced_link_ = new views::Link(
310 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_ADVANCED));
312 advanced_link_->set_listener(this);
313 advanced_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
316 void OneClickSigninBubbleView::InitLearnMoreLink() {
317 learn_more_link_ = new views::Link(
318 l10n_util::GetStringUTF16(IDS_LEARN_MORE));
319 learn_more_link_->set_listener(this);
320 learn_more_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
323 bool OneClickSigninBubbleView::AcceleratorPressed(
324 const ui::Accelerator& accelerator) {
325 if (accelerator.key_code() == ui::VKEY_RETURN ||
326 accelerator.key_code() == ui::VKEY_ESCAPE) {
327 OneClickSigninBubbleView::Hide();
329 if (is_sync_dialog_) {
330 if (accelerator.key_code() == ui::VKEY_RETURN) {
331 OneClickSigninHelper::LogConfirmHistogramValue(
332 clicked_learn_more_ ?
333 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_RETURN :
334 one_click_signin::HISTOGRAM_CONFIRM_RETURN);
336 base::ResetAndReturn(&start_sync_callback_).Run(
337 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS);
338 } else if (accelerator.key_code() == ui::VKEY_ESCAPE) {
339 OneClickSigninHelper::LogConfirmHistogramValue(
340 clicked_learn_more_ ?
341 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_ESCAPE :
342 one_click_signin::HISTOGRAM_CONFIRM_ESCAPE);
344 base::ResetAndReturn(&start_sync_callback_).Run(
345 OneClickSigninSyncStarter::UNDO_SYNC);
352 return BubbleDelegateView::AcceleratorPressed(accelerator);
355 void OneClickSigninBubbleView::LinkClicked(views::Link* source,
357 if (source == learn_more_link_) {
358 if (is_sync_dialog_ && !clicked_learn_more_) {
359 OneClickSigninHelper::LogConfirmHistogramValue(
360 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE);
361 clicked_learn_more_ = true;
363 delegate_->OnLearnMoreLinkClicked(is_sync_dialog_);
365 // don't hide the modal dialog, as this is an informational link
368 } else if (advanced_link_ && source == advanced_link_) {
369 if (is_sync_dialog_) {
370 OneClickSigninHelper::LogConfirmHistogramValue(
371 clicked_learn_more_ ?
372 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_ADVANCED :
373 one_click_signin::HISTOGRAM_CONFIRM_ADVANCED);
375 base::ResetAndReturn(&start_sync_callback_).Run(
376 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
378 delegate_->OnAdvancedLinkClicked();
385 void OneClickSigninBubbleView::ButtonPressed(views::Button* sender,
386 const ui::Event& event) {
389 if (is_sync_dialog_) {
390 if (sender == ok_button_)
391 OneClickSigninHelper::LogConfirmHistogramValue(
392 clicked_learn_more_ ?
393 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_OK :
394 one_click_signin::HISTOGRAM_CONFIRM_OK);
396 if (sender == undo_button_)
397 OneClickSigninHelper::LogConfirmHistogramValue(
398 clicked_learn_more_ ?
399 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_UNDO :
400 one_click_signin::HISTOGRAM_CONFIRM_UNDO);
402 if (sender == close_button_)
403 OneClickSigninHelper::LogConfirmHistogramValue(
404 clicked_learn_more_ ?
405 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_CLOSE :
406 one_click_signin::HISTOGRAM_CONFIRM_CLOSE);
408 base::ResetAndReturn(&start_sync_callback_).Run((sender == ok_button_) ?
409 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS :
410 OneClickSigninSyncStarter::UNDO_SYNC);
414 void OneClickSigninBubbleView::WindowClosing() {
415 // We have to reset |bubble_view_| here, not in our destructor, because
416 // we'll be destroyed asynchronously and the shown state will be checked
418 DCHECK_EQ(bubble_view_, this);
421 if (is_sync_dialog_ && !start_sync_callback_.is_null()) {
422 base::ResetAndReturn(&start_sync_callback_).Run(
423 OneClickSigninSyncStarter::UNDO_SYNC);