Home | History | Annotate | Download | only in sync
      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