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