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 set_border(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 // Add title message.
180 views::ColumnSet
* cs
= layout
->AddColumnSet(COLUMN_SET_TITLE_BAR
);
181 cs
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::LEADING
, 0,
182 views::GridLayout::USE_PREF
, 0, 0);
184 layout
->StartRow(0, COLUMN_SET_TITLE_BAR
);
186 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
187 views::Label
* label
= new views::Label(
188 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_TITLE_NEW
),
189 rb
.GetFontList(ui::ResourceBundle::MediumFont
));
190 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
191 layout
->AddView(label
);
194 layout
->AddPaddingRow(0, views::kUnrelatedControlLargeVerticalSpacing
);
196 // Add main text description.
197 layout
->StartRow(0, COLUMN_SET_FILL_ALIGN
);
199 views::Label
* label
= !error_message_
.empty() ?
200 new views::Label(error_message_
) :
202 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_BUBBLE_MESSAGE
));
204 label
->SetMultiLine(true);
205 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
206 label
->SizeToFit(kMinimumLabelWidth
);
207 layout
->AddView(label
);
209 layout
->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing
);
211 layout
->StartRow(0, COLUMN_SET_CONTROLS
);
214 layout
->AddView(learn_more_link_
);
217 void OneClickSigninBubbleView::InitDialogContent(views::GridLayout
* layout
) {
218 OneClickSigninHelper::LogConfirmHistogramValue(
219 one_click_signin::HISTOGRAM_CONFIRM_SHOWN
);
221 // Column set for title bar.
222 views::ColumnSet
* cs
= layout
->AddColumnSet(COLUMN_SET_TITLE_BAR
);
223 cs
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::CENTER
, 0,
224 views::GridLayout::USE_PREF
, 0, 0);
225 cs
->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing
);
226 cs
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::CENTER
, 0,
227 views::GridLayout::USE_PREF
, 0, 0);
229 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
232 layout
->StartRow(0, COLUMN_SET_TITLE_BAR
);
234 views::Label
* label
= new views::Label(
235 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_TITLE_NEW
),
236 rb
.GetFontList(ui::ResourceBundle::MediumBoldFont
));
237 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
238 layout
->AddView(label
);
240 close_button_
= new views::ImageButton(this);
241 close_button_
->SetImage(views::ImageButton::STATE_NORMAL
,
242 rb
.GetImageNamed(IDR_CLOSE_2
).ToImageSkia());
243 close_button_
->SetImage(views::ImageButton::STATE_HOVERED
,
244 rb
.GetImageNamed(IDR_CLOSE_2_H
).ToImageSkia());
245 close_button_
->SetImage(views::ImageButton::STATE_PRESSED
,
246 rb
.GetImageNamed(IDR_CLOSE_2_P
).ToImageSkia());
248 layout
->AddView(close_button_
);
251 layout
->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing
);
254 layout
->StartRow(0, COLUMN_SET_FILL_ALIGN
);
256 views::Label
* label
= new views::Label(
257 l10n_util::GetStringFUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_MESSAGE_NEW
,
259 label
->SetMultiLine(true);
260 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
261 label
->SizeToFit(kMinimumDialogLabelWidth
);
262 layout
->AddView(label
);
264 layout
->StartRow(0, COLUMN_SET_FILL_ALIGN
);
267 layout
->AddView(learn_more_link_
, 1, 1, views::GridLayout::TRAILING
,
268 views::GridLayout::CENTER
);
271 layout
->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing
);
274 void OneClickSigninBubbleView::InitButtons(views::GridLayout
* layout
) {
275 GetButtons(&ok_button_
, &undo_button_
);
276 layout
->AddView(ok_button_
);
279 layout
->AddView(undo_button_
);
282 void OneClickSigninBubbleView::GetButtons(views::LabelButton
** ok_button
,
283 views::LabelButton
** undo_button
) {
284 base::string16 ok_label
=
285 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_OK_BUTTON
);
287 *ok_button
= new views::LabelButton(this, ok_label
);
288 (*ok_button
)->SetStyle(views::Button::STYLE_BUTTON
);
290 // The default size of the buttons is too large. To allow them to be smaller
291 // ignore the minimum default size.,
292 (*ok_button
)->set_min_size(gfx::Size());
294 if (is_sync_dialog_
) {
295 *undo_button
= new views::LabelButton(this, base::string16());
296 (*undo_button
)->SetStyle(views::Button::STYLE_BUTTON
);
297 (*undo_button
)->set_min_size(gfx::Size());
299 base::string16 undo_label
=
300 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_UNDO_BUTTON
);
301 (*undo_button
)->SetText(undo_label
);
305 void OneClickSigninBubbleView::InitAdvancedLink() {
306 advanced_link_
= new views::Link(
307 l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_ADVANCED
));
309 advanced_link_
->set_listener(this);
310 advanced_link_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
313 void OneClickSigninBubbleView::InitLearnMoreLink() {
314 learn_more_link_
= new views::Link(
315 l10n_util::GetStringUTF16(IDS_LEARN_MORE
));
316 learn_more_link_
->set_listener(this);
317 learn_more_link_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
320 bool OneClickSigninBubbleView::AcceleratorPressed(
321 const ui::Accelerator
& accelerator
) {
322 if (accelerator
.key_code() == ui::VKEY_RETURN
||
323 accelerator
.key_code() == ui::VKEY_ESCAPE
) {
324 OneClickSigninBubbleView::Hide();
326 if (is_sync_dialog_
) {
327 if (accelerator
.key_code() == ui::VKEY_RETURN
) {
328 OneClickSigninHelper::LogConfirmHistogramValue(
329 clicked_learn_more_
?
330 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_RETURN
:
331 one_click_signin::HISTOGRAM_CONFIRM_RETURN
);
333 base::ResetAndReturn(&start_sync_callback_
).Run(
334 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS
);
335 } else if (accelerator
.key_code() == ui::VKEY_ESCAPE
) {
336 OneClickSigninHelper::LogConfirmHistogramValue(
337 clicked_learn_more_
?
338 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_ESCAPE
:
339 one_click_signin::HISTOGRAM_CONFIRM_ESCAPE
);
341 base::ResetAndReturn(&start_sync_callback_
).Run(
342 OneClickSigninSyncStarter::UNDO_SYNC
);
349 return BubbleDelegateView::AcceleratorPressed(accelerator
);
352 void OneClickSigninBubbleView::LinkClicked(views::Link
* source
,
354 if (source
== learn_more_link_
) {
355 if (is_sync_dialog_
&& !clicked_learn_more_
) {
356 OneClickSigninHelper::LogConfirmHistogramValue(
357 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE
);
358 clicked_learn_more_
= true;
360 delegate_
->OnLearnMoreLinkClicked(is_sync_dialog_
);
362 // don't hide the modal dialog, as this is an informational link
365 } else if (advanced_link_
&& source
== advanced_link_
) {
366 if (is_sync_dialog_
) {
367 OneClickSigninHelper::LogConfirmHistogramValue(
368 clicked_learn_more_
?
369 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_ADVANCED
:
370 one_click_signin::HISTOGRAM_CONFIRM_ADVANCED
);
372 base::ResetAndReturn(&start_sync_callback_
).Run(
373 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST
);
375 delegate_
->OnAdvancedLinkClicked();
382 void OneClickSigninBubbleView::ButtonPressed(views::Button
* sender
,
383 const ui::Event
& event
) {
386 if (is_sync_dialog_
) {
387 if (sender
== ok_button_
)
388 OneClickSigninHelper::LogConfirmHistogramValue(
389 clicked_learn_more_
?
390 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_OK
:
391 one_click_signin::HISTOGRAM_CONFIRM_OK
);
393 if (sender
== undo_button_
)
394 OneClickSigninHelper::LogConfirmHistogramValue(
395 clicked_learn_more_
?
396 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_UNDO
:
397 one_click_signin::HISTOGRAM_CONFIRM_UNDO
);
399 if (sender
== close_button_
)
400 OneClickSigninHelper::LogConfirmHistogramValue(
401 clicked_learn_more_
?
402 one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_CLOSE
:
403 one_click_signin::HISTOGRAM_CONFIRM_CLOSE
);
405 base::ResetAndReturn(&start_sync_callback_
).Run((sender
== ok_button_
) ?
406 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS
:
407 OneClickSigninSyncStarter::UNDO_SYNC
);
411 void OneClickSigninBubbleView::WindowClosing() {
412 // We have to reset |bubble_view_| here, not in our destructor, because
413 // we'll be destroyed asynchronously and the shown state will be checked
415 DCHECK_EQ(bubble_view_
, this);
418 if (is_sync_dialog_
&& !start_sync_callback_
.is_null()) {
419 base::ResetAndReturn(&start_sync_callback_
).Run(
420 OneClickSigninSyncStarter::UNDO_SYNC
);