Home | History | Annotate | Download | only in profiles
      1 // Copyright 2014 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/profiles/profile_reset_bubble_view.h"
      6 
      7 #include "chrome/app/chrome_command_ids.h"
      8 #include "chrome/browser/profile_resetter/profile_reset_global_error.h"
      9 #include "chrome/browser/profile_resetter/resettable_settings_snapshot.h"
     10 #include "chrome/browser/ui/global_error/global_error_service.h"
     11 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
     12 #include "chrome/browser/ui/profile_reset_bubble.h"
     13 #include "chrome/browser/ui/views/frame/browser_view.h"
     14 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
     15 #include "chrome/common/url_constants.h"
     16 #include "components/google/core/browser/google_util.h"
     17 #include "content/public/browser/page_navigator.h"
     18 #include "content/public/browser/user_metrics.h"
     19 #include "grit/chromium_strings.h"
     20 #include "grit/components_strings.h"
     21 #include "grit/generated_resources.h"
     22 #include "grit/theme_resources.h"
     23 #include "ui/base/l10n/l10n_util.h"
     24 #include "ui/base/resource/resource_bundle.h"
     25 #include "ui/gfx/image/image_skia_operations.h"
     26 #include "ui/views/background.h"
     27 #include "ui/views/controls/button/checkbox.h"
     28 #include "ui/views/controls/button/image_button.h"
     29 #include "ui/views/controls/button/label_button.h"
     30 #include "ui/views/controls/label.h"
     31 #include "ui/views/controls/link.h"
     32 #include "ui/views/controls/scroll_view.h"
     33 #include "ui/views/controls/separator.h"
     34 #include "ui/views/layout/grid_layout.h"
     35 #include "ui/views/layout/layout_constants.h"
     36 
     37 using views::GridLayout;
     38 
     39 namespace {
     40 
     41 // Fixed width of the column holding the description label of the bubble.
     42 const int kWidthOfDescriptionText = 370;
     43 
     44 // Margins width for the top rows to compensate for the bottom panel for which
     45 // we don't want any margin.
     46 const int kMarginWidth = 12;
     47 const int kMarginHeight = kMarginWidth;
     48 
     49 // Width of a colum in the FeedbackView.
     50 const int kFeedbackViewColumnWidth = kWidthOfDescriptionText / 2 + kMarginWidth;
     51 
     52 // Width of the column used to disaplay the help button.
     53 const int kHelpButtonColumnWidth = 30;
     54 
     55 // Width of the reporting checkbox column.
     56 const int kReportingCheckboxColumnWidth =
     57     kWidthOfDescriptionText + 2 * kMarginWidth;
     58 
     59 // Full width including all columns.
     60 const int kAllColumnsWidth =
     61     kReportingCheckboxColumnWidth + kHelpButtonColumnWidth;
     62 
     63 // Maximum height of the scrollable feedback view.
     64 const int kMaxFeedbackViewHeight = 450;
     65 
     66 // The vertical padding between two values in the feedback view.
     67 const int kInterFeedbackValuePadding = 4;
     68 
     69 // We subtract 2 to account for the natural button padding, and
     70 // to bring the separation visually in line with the row separation
     71 // height.
     72 const int kButtonPadding = views::kRelatedButtonHSpacing - 2;
     73 
     74 // The color of the background of the sub panel to report current settings.
     75 const SkColor kLightGrayBackgroundColor = 0xFFF5F5F5;
     76 
     77 // This view is used to contain the scrollable contents that are shown the user
     78 // to expose what feedback will be sent back to Google.
     79 class FeedbackView : public views::View {
     80  public:
     81   FeedbackView() {}
     82 
     83   // Setup the layout manager of the Feedback view using the content of the
     84   // |feedback| ListValue which contains a list of key/value pairs stored in
     85   // DictionaryValues. The key is to be displayed right aligned on the left, and
     86   // the value as a left aligned multiline text on the right.
     87   void SetupLayoutManager(const base::ListValue& feedback) {
     88     RemoveAllChildViews(true);
     89     set_background(views::Background::CreateSolidBackground(
     90         kLightGrayBackgroundColor));
     91 
     92     GridLayout* layout = new GridLayout(this);
     93     SetLayoutManager(layout);
     94 
     95     // We only need a single column set for left/right text and middle margin.
     96     views::ColumnSet* cs = layout->AddColumnSet(0);
     97     cs->AddColumn(GridLayout::FILL, GridLayout::LEADING, 1,
     98                   GridLayout::FIXED, kFeedbackViewColumnWidth, 0);
     99     cs->AddPaddingColumn(0, kMarginWidth);
    100     cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
    101                   GridLayout::FIXED, kFeedbackViewColumnWidth, 0);
    102     for (size_t i = 0; i < feedback.GetSize(); ++i) {
    103       const base::DictionaryValue* dictionary = NULL;
    104       if (!feedback.GetDictionary(i, &dictionary) || !dictionary)
    105         continue;
    106 
    107       base::string16 key;
    108       if (!dictionary->GetString("key", &key))
    109         continue;
    110 
    111       base::string16 value;
    112       if (!dictionary->GetString("value", &value))
    113         continue;
    114 
    115       // The key is shown on the left, multi-line (required to allow wrapping in
    116       // case the key name does not fit), and right-aligned.
    117       views::Label* left_text_label = new views::Label(key);
    118       left_text_label->SetMultiLine(true);
    119       left_text_label->SetEnabledColor(SK_ColorGRAY);
    120       left_text_label->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
    121 
    122       // The value is shown on the right, multi-line, left-aligned.
    123       views::Label* right_text_label = new views::Label(value);
    124       right_text_label->SetMultiLine(true);
    125       right_text_label->SetEnabledColor(SK_ColorDKGRAY);
    126       right_text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    127 
    128       layout->StartRow(0, 0);
    129       layout->AddView(left_text_label);
    130       layout->AddView(right_text_label);
    131       layout->AddPaddingRow(0, kInterFeedbackValuePadding);
    132     }
    133 
    134     // We need to set our size to our preferred size because our parent is a
    135     // scroll view and doesn't know which size to set us to. Also since our
    136     // parent scrolls, we are not bound to its size. So our size is based on the
    137     // size computed by the our layout manager, which is what
    138     // SizeToPreferredSize() does.
    139     SizeToPreferredSize();
    140   }
    141 
    142  private:
    143   DISALLOW_COPY_AND_ASSIGN(FeedbackView);
    144 };
    145 
    146 }  // namespace
    147 
    148 // ProfileResetBubbleView ---------------------------------------------------
    149 
    150 // static
    151 ProfileResetBubbleView* ProfileResetBubbleView::ShowBubble(
    152     const base::WeakPtr<ProfileResetGlobalError>& global_error,
    153     Browser* browser) {
    154   views::View* anchor_view =
    155       BrowserView::GetBrowserViewForBrowser(browser)->toolbar()->app_menu();
    156   ProfileResetBubbleView* reset_bubble = new ProfileResetBubbleView(
    157       global_error, anchor_view, browser, browser->profile());
    158   views::BubbleDelegateView::CreateBubble(reset_bubble)->Show();
    159   content::RecordAction(base::UserMetricsAction("SettingsResetBubble.Show"));
    160   return reset_bubble;
    161 }
    162 
    163 ProfileResetBubbleView::~ProfileResetBubbleView() {}
    164 
    165 views::View* ProfileResetBubbleView::GetInitiallyFocusedView() {
    166   return controls_.reset_button;
    167 }
    168 
    169 void ProfileResetBubbleView::WindowClosing() {
    170   if (global_error_)
    171     global_error_->OnBubbleViewDidClose();
    172 }
    173 
    174 ProfileResetBubbleView::ProfileResetBubbleView(
    175     const base::WeakPtr<ProfileResetGlobalError>& global_error,
    176     views::View* anchor_view,
    177     content::PageNavigator* navigator,
    178     Profile* profile)
    179     : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
    180       navigator_(navigator),
    181       profile_(profile),
    182       global_error_(global_error),
    183       resetting_(false),
    184       chose_to_reset_(false),
    185       show_help_pane_(false),
    186       weak_factory_(this) {
    187 }
    188 
    189 void ProfileResetBubbleView::ResetAllChildren() {
    190   controls_.Reset();
    191   SetLayoutManager(NULL);
    192   RemoveAllChildViews(true);
    193 }
    194 
    195 void ProfileResetBubbleView::Init() {
    196   set_margins(gfx::Insets(kMarginHeight, 0, 0, 0));
    197   // Start requesting the feedback data.
    198   snapshot_.reset(new ResettableSettingsSnapshot(profile_));
    199   snapshot_->RequestShortcuts(
    200       base::Bind(&ProfileResetBubbleView::UpdateFeedbackDetails,
    201                  weak_factory_.GetWeakPtr()));
    202   SetupLayoutManager(true);
    203 }
    204 
    205 void ProfileResetBubbleView::SetupLayoutManager(bool report_checked) {
    206   ResetAllChildren();
    207 
    208   base::string16 product_name(
    209       l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
    210   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    211 
    212   // Bubble title label.
    213   views::Label* title_label = new views::Label(
    214       l10n_util::GetStringFUTF16(IDS_RESET_BUBBLE_TITLE, product_name),
    215       rb.GetFontList(ui::ResourceBundle::BoldFont));
    216   title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    217 
    218   // Description text label.
    219   views::Label* text_label = new views::Label(
    220       l10n_util::GetStringFUTF16(IDS_RESET_BUBBLE_TEXT, product_name));
    221   text_label->SetMultiLine(true);
    222   text_label->SetLineHeight(20);
    223   text_label->SetEnabledColor(SK_ColorDKGRAY);
    224   text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    225 
    226   // Learn more link.
    227   views::Link* learn_more_link = new views::Link(
    228       l10n_util::GetStringUTF16(IDS_LEARN_MORE));
    229   learn_more_link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    230   learn_more_link->set_listener(this);
    231   learn_more_link->SetUnderline(false);
    232 
    233   // Reset button's name is based on |resetting_| state.
    234   int reset_button_string_id = IDS_RESET_PROFILE_SETTINGS_COMMIT_BUTTON;
    235   if (resetting_)
    236     reset_button_string_id = IDS_RESETTING;
    237   controls_.reset_button = new views::LabelButton(
    238       this, l10n_util::GetStringUTF16(reset_button_string_id));
    239   controls_.reset_button->SetStyle(views::Button::STYLE_BUTTON);
    240   controls_.reset_button->SetIsDefault(true);
    241   controls_.reset_button->SetFontList(
    242       rb.GetFontList(ui::ResourceBundle::BoldFont));
    243   controls_.reset_button->SetEnabled(!resetting_);
    244   // For the Resetting... text to fit.
    245   gfx::Size reset_button_size = controls_.reset_button->GetPreferredSize();
    246   reset_button_size.set_width(100);
    247   controls_.reset_button->SetMinSize(reset_button_size);
    248 
    249   // No thanks button.
    250   controls_.no_thanks_button = new views::LabelButton(
    251       this, l10n_util::GetStringUTF16(IDS_NO_THANKS));
    252   controls_.no_thanks_button->SetStyle(views::Button::STYLE_BUTTON);
    253   controls_.no_thanks_button->SetEnabled(!resetting_);
    254 
    255   // Checkbox for reporting settings or not.
    256   controls_.report_settings_checkbox = new views::Checkbox(
    257       l10n_util::GetStringUTF16(IDS_REPORT_BUBBLE_TEXT));
    258   controls_.report_settings_checkbox->SetTextColor(
    259       views::Button::STATE_NORMAL, SK_ColorGRAY);
    260   controls_.report_settings_checkbox->SetChecked(report_checked);
    261   controls_.report_settings_checkbox->SetTextMultiLine(true);
    262   controls_.report_settings_checkbox->set_background(
    263       views::Background::CreateSolidBackground(kLightGrayBackgroundColor));
    264   // Have a smaller margin on the right, to have the |controls_.help_button|
    265   // closer to the edge.
    266   controls_.report_settings_checkbox->SetBorder(
    267       views::Border::CreateSolidSidedBorder(kMarginWidth,
    268                                             kMarginWidth,
    269                                             kMarginWidth,
    270                                             kMarginWidth / 2,
    271                                             kLightGrayBackgroundColor));
    272 
    273   // Help button to toggle the bottom panel on or off.
    274   controls_.help_button = new views::ImageButton(this);
    275   const gfx::ImageSkia* help_image = rb.GetImageSkiaNamed(IDR_QUESTION_MARK);
    276   color_utils::HSL hsl_shift = { -1, 0, 0.8 };
    277   brighter_help_image_ = gfx::ImageSkiaOperations::CreateHSLShiftedImage(
    278       *help_image, hsl_shift);
    279   controls_.help_button->SetImage(
    280       views::Button::STATE_NORMAL, &brighter_help_image_);
    281   controls_.help_button->SetImage(views::Button::STATE_HOVERED, help_image);
    282   controls_.help_button->SetImage(views::Button::STATE_PRESSED, help_image);
    283   controls_.help_button->set_background(
    284       views::Background::CreateSolidBackground(kLightGrayBackgroundColor));
    285   controls_.help_button->SetImageAlignment(views::ImageButton::ALIGN_CENTER,
    286                                   views::ImageButton::ALIGN_MIDDLE);
    287 
    288   GridLayout* layout = new GridLayout(this);
    289   SetLayoutManager(layout);
    290 
    291   // Title row.
    292   const int kTitleColumnSetId = 0;
    293   views::ColumnSet* cs = layout->AddColumnSet(kTitleColumnSetId);
    294   cs->AddPaddingColumn(0, kMarginWidth);
    295   cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
    296                 GridLayout::USE_PREF, 0, 0);
    297   cs->AddPaddingColumn(0, kMarginWidth);
    298 
    299   // Text row.
    300   const int kTextColumnSetId = 1;
    301   cs = layout->AddColumnSet(kTextColumnSetId);
    302   cs->AddPaddingColumn(0, kMarginWidth);
    303   cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
    304                 GridLayout::FIXED, kWidthOfDescriptionText, 0);
    305   cs->AddPaddingColumn(0, kMarginWidth);
    306 
    307   // Learn more link & buttons row.
    308   const int kButtonsColumnSetId = 2;
    309   cs = layout->AddColumnSet(kButtonsColumnSetId);
    310   cs->AddPaddingColumn(0, kMarginWidth);
    311   cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
    312                 GridLayout::USE_PREF, 0, 0);
    313   cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing);
    314   cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
    315                 GridLayout::USE_PREF, 0, 0);
    316   cs->AddPaddingColumn(0, kButtonPadding);
    317   cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
    318                 GridLayout::USE_PREF, 0, 0);
    319   cs->AddPaddingColumn(0, kMarginWidth);
    320 
    321   // Separator.
    322   const int kSeparatorColumnSetId = 3;
    323   cs = layout->AddColumnSet(kSeparatorColumnSetId);
    324   cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
    325                 GridLayout::FIXED, kAllColumnsWidth, 0);
    326 
    327   // Reporting row.
    328   const int kReportColumnSetId = 4;
    329   cs = layout->AddColumnSet(kReportColumnSetId);
    330   cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
    331                 GridLayout::FIXED, kReportingCheckboxColumnWidth, 0);
    332   cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
    333                 GridLayout::FIXED, kHelpButtonColumnWidth, 0);
    334 
    335   layout->StartRow(0, kTitleColumnSetId);
    336   layout->AddView(title_label);
    337   layout->AddPaddingRow(0, kMarginHeight);
    338 
    339   layout->StartRow(0, kTextColumnSetId);
    340   layout->AddView(text_label);
    341   layout->AddPaddingRow(0, kMarginHeight);
    342 
    343   layout->StartRow(0, kButtonsColumnSetId);
    344   layout->AddView(learn_more_link);
    345   layout->AddView(controls_.reset_button);
    346   layout->AddView(controls_.no_thanks_button);
    347   layout->AddPaddingRow(0, kMarginHeight);
    348 
    349   layout->StartRow(0, kSeparatorColumnSetId);
    350   layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
    351 
    352   layout->StartRow(0, kReportColumnSetId);
    353   layout->AddView(controls_.report_settings_checkbox);
    354   layout->AddView(controls_.help_button);
    355 
    356   if (show_help_pane_ && snapshot_) {
    357     // We need a single row to add the scroll view containing the feedback.
    358     const int kReportDetailsColumnSetId = 5;
    359     cs = layout->AddColumnSet(kReportDetailsColumnSetId);
    360     cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
    361                   GridLayout::USE_PREF, 0, 0);
    362 
    363     FeedbackView* feedback_view = new FeedbackView();
    364     scoped_ptr<base::ListValue> feedback_data =
    365         GetReadableFeedbackForSnapshot(profile_, *snapshot_);
    366     feedback_view->SetupLayoutManager(*feedback_data);
    367 
    368     views::ScrollView* scroll_view = new views::ScrollView();
    369     scroll_view->set_background(views::Background::CreateSolidBackground(
    370         kLightGrayBackgroundColor));
    371     scroll_view->SetContents(feedback_view);
    372 
    373     layout->StartRow(1, kReportDetailsColumnSetId);
    374     layout->AddView(scroll_view, 1, 1, GridLayout::FILL,
    375                     GridLayout::FILL, kAllColumnsWidth,
    376                     std::min(feedback_view->height() + kMarginHeight,
    377                              kMaxFeedbackViewHeight));
    378   }
    379 
    380   Layout();
    381   AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
    382 }
    383 
    384 void ProfileResetBubbleView::ButtonPressed(views::Button* sender,
    385                                            const ui::Event& event) {
    386   if (sender == controls_.reset_button) {
    387     DCHECK(!resetting_);
    388     content::RecordAction(
    389         base::UserMetricsAction("SettingsResetBubble.Reset"));
    390 
    391     // Remember that the user chose to reset, and that resetting is underway.
    392     chose_to_reset_ = true;
    393     resetting_ = true;
    394 
    395     controls_.reset_button->SetText(l10n_util::GetStringUTF16(IDS_RESETTING));
    396     controls_.reset_button->SetEnabled(false);
    397     controls_.no_thanks_button->SetEnabled(false);
    398     SchedulePaint();
    399 
    400     if (global_error_) {
    401       global_error_->OnBubbleViewResetButtonPressed(
    402           controls_.report_settings_checkbox->checked());
    403     }
    404   } else if (sender == controls_.no_thanks_button) {
    405     DCHECK(!resetting_);
    406     content::RecordAction(
    407         base::UserMetricsAction("SettingsResetBubble.NoThanks"));
    408 
    409     if (global_error_)
    410       global_error_->OnBubbleViewNoThanksButtonPressed();
    411     GetWidget()->Close();
    412     return;
    413   } else if (sender == controls_.help_button) {
    414     show_help_pane_ = !show_help_pane_;
    415 
    416     SetupLayoutManager(controls_.report_settings_checkbox->checked());
    417     SizeToContents();
    418   }
    419 }
    420 
    421 void ProfileResetBubbleView::LinkClicked(views::Link* source, int flags) {
    422   content::RecordAction(
    423       base::UserMetricsAction("SettingsResetBubble.LearnMore"));
    424   navigator_->OpenURL(content::OpenURLParams(
    425       GURL(chrome::kResetProfileSettingsLearnMoreURL), content::Referrer(),
    426       NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK, false));
    427 }
    428 
    429 void ProfileResetBubbleView::CloseBubbleView() {
    430   resetting_ = false;
    431   GetWidget()->Close();
    432 }
    433 
    434 void ProfileResetBubbleView::UpdateFeedbackDetails() {
    435   if (show_help_pane_)
    436     SetupLayoutManager(controls_.report_settings_checkbox->checked());
    437 }
    438 
    439 bool IsProfileResetBubbleSupported() {
    440   return true;
    441 }
    442 
    443 GlobalErrorBubbleViewBase* ShowProfileResetBubble(
    444     const base::WeakPtr<ProfileResetGlobalError>& global_error,
    445     Browser* browser) {
    446   return ProfileResetBubbleView::ShowBubble(global_error, browser);
    447 }
    448