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