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