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