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.
5 #include "chrome/browser/ui/views/profiles/profile_reset_bubble_view.h"
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/views/frame/browser_view.h"
13 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
14 #include "chrome/browser/ui/views/toolbar/wrench_toolbar_button.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"
37 using views::GridLayout
;
41 // Fixed width of the column holding the description label of the bubble.
42 const int kWidthOfDescriptionText
= 370;
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
;
49 // Width of a colum in the FeedbackView.
50 const int kFeedbackViewColumnWidth
= kWidthOfDescriptionText
/ 2 + kMarginWidth
;
52 // Width of the column used to disaplay the help button.
53 const int kHelpButtonColumnWidth
= 30;
55 // Width of the reporting checkbox column.
56 const int kReportingCheckboxColumnWidth
=
57 kWidthOfDescriptionText
+ 2 * kMarginWidth
;
59 // Full width including all columns.
60 const int kAllColumnsWidth
=
61 kReportingCheckboxColumnWidth
+ kHelpButtonColumnWidth
;
63 // Maximum height of the scrollable feedback view.
64 const int kMaxFeedbackViewHeight
= 450;
66 // The vertical padding between two values in the feedback view.
67 const int kInterFeedbackValuePadding
= 4;
69 // We subtract 2 to account for the natural button padding, and
70 // to bring the separation visually in line with the row separation
72 const int kButtonPadding
= views::kRelatedButtonHSpacing
- 2;
74 // The color of the background of the sub panel to report current settings.
75 const SkColor kLightGrayBackgroundColor
= 0xFFF5F5F5;
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
{
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
));
92 GridLayout
* layout
= new GridLayout(this);
93 SetLayoutManager(layout
);
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
)
108 if (!dictionary
->GetString("key", &key
))
111 base::string16 value
;
112 if (!dictionary
->GetString("value", &value
))
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
);
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
);
128 layout
->StartRow(0, 0);
129 layout
->AddView(left_text_label
);
130 layout
->AddView(right_text_label
);
131 layout
->AddPaddingRow(0, kInterFeedbackValuePadding
);
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();
143 DISALLOW_COPY_AND_ASSIGN(FeedbackView
);
148 // ProfileResetBubbleView ---------------------------------------------------
151 ProfileResetBubbleView
* ProfileResetBubbleView::ShowBubble(
152 const base::WeakPtr
<ProfileResetGlobalError
>& global_error
,
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"));
163 ProfileResetBubbleView::~ProfileResetBubbleView() {}
165 views::View
* ProfileResetBubbleView::GetInitiallyFocusedView() {
166 return controls_
.reset_button
;
169 void ProfileResetBubbleView::WindowClosing() {
171 global_error_
->OnBubbleViewDidClose();
174 ProfileResetBubbleView::ProfileResetBubbleView(
175 const base::WeakPtr
<ProfileResetGlobalError
>& global_error
,
176 views::View
* anchor_view
,
177 content::PageNavigator
* navigator
,
179 : BubbleDelegateView(anchor_view
, views::BubbleBorder::TOP_RIGHT
),
180 navigator_(navigator
),
182 global_error_(global_error
),
184 chose_to_reset_(false),
185 show_help_pane_(false),
186 weak_factory_(this) {
189 void ProfileResetBubbleView::ResetAllChildren() {
191 SetLayoutManager(NULL
);
192 RemoveAllChildViews(true);
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);
205 void ProfileResetBubbleView::SetupLayoutManager(bool report_checked
) {
208 base::string16
product_name(
209 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME
));
210 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
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
);
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
);
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);
233 // Reset button's name is based on |resetting_| state.
234 int reset_button_string_id
= IDS_RESET_PROFILE_SETTINGS_COMMIT_BUTTON
;
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
);
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_
);
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
,
271 kLightGrayBackgroundColor
));
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
);
288 GridLayout
* layout
= new GridLayout(this);
289 SetLayoutManager(layout
);
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
);
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
);
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
);
322 const int kSeparatorColumnSetId
= 3;
323 cs
= layout
->AddColumnSet(kSeparatorColumnSetId
);
324 cs
->AddColumn(GridLayout::FILL
, GridLayout::FILL
, 0,
325 GridLayout::FIXED
, kAllColumnsWidth
, 0);
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);
335 layout
->StartRow(0, kTitleColumnSetId
);
336 layout
->AddView(title_label
);
337 layout
->AddPaddingRow(0, kMarginHeight
);
339 layout
->StartRow(0, kTextColumnSetId
);
340 layout
->AddView(text_label
);
341 layout
->AddPaddingRow(0, kMarginHeight
);
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
);
349 layout
->StartRow(0, kSeparatorColumnSetId
);
350 layout
->AddView(new views::Separator(views::Separator::HORIZONTAL
));
352 layout
->StartRow(0, kReportColumnSetId
);
353 layout
->AddView(controls_
.report_settings_checkbox
);
354 layout
->AddView(controls_
.help_button
);
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);
363 FeedbackView
* feedback_view
= new FeedbackView();
364 scoped_ptr
<base::ListValue
> feedback_data
=
365 GetReadableFeedbackForSnapshot(profile_
, *snapshot_
);
366 feedback_view
->SetupLayoutManager(*feedback_data
);
368 views::ScrollView
* scroll_view
= new views::ScrollView();
369 scroll_view
->set_background(views::Background::CreateSolidBackground(
370 kLightGrayBackgroundColor
));
371 scroll_view
->SetContents(feedback_view
);
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
));
381 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN
, ui::EF_NONE
));
384 void ProfileResetBubbleView::ButtonPressed(views::Button
* sender
,
385 const ui::Event
& event
) {
386 if (sender
== controls_
.reset_button
) {
388 content::RecordAction(
389 base::UserMetricsAction("SettingsResetBubble.Reset"));
391 // Remember that the user chose to reset, and that resetting is underway.
392 chose_to_reset_
= true;
395 controls_
.reset_button
->SetText(l10n_util::GetStringUTF16(IDS_RESETTING
));
396 controls_
.reset_button
->SetEnabled(false);
397 controls_
.no_thanks_button
->SetEnabled(false);
401 global_error_
->OnBubbleViewResetButtonPressed(
402 controls_
.report_settings_checkbox
->checked());
404 } else if (sender
== controls_
.no_thanks_button
) {
406 content::RecordAction(
407 base::UserMetricsAction("SettingsResetBubble.NoThanks"));
410 global_error_
->OnBubbleViewNoThanksButtonPressed();
411 GetWidget()->Close();
413 } else if (sender
== controls_
.help_button
) {
414 show_help_pane_
= !show_help_pane_
;
416 SetupLayoutManager(controls_
.report_settings_checkbox
->checked());
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));
429 void ProfileResetBubbleView::CloseBubbleView() {
431 GetWidget()->Close();
434 void ProfileResetBubbleView::UpdateFeedbackDetails() {
436 SetupLayoutManager(controls_
.report_settings_checkbox
->checked());