1 // Copyright (c) 2013 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/conflicting_module_view_win.h" 6 7 #include "base/metrics/histogram.h" 8 #include "chrome/browser/chrome_notification_types.h" 9 #include "chrome/browser/enumerate_modules_model_win.h" 10 #include "chrome/browser/profiles/profile.h" 11 #include "chrome/browser/ui/browser.h" 12 #include "chrome/common/pref_names.h" 13 #include "chrome/grit/chromium_strings.h" 14 #include "chrome/grit/generated_resources.h" 15 #include "chrome/grit/locale_settings.h" 16 #include "content/public/browser/notification_service.h" 17 #include "content/public/browser/user_metrics.h" 18 #include "grit/theme_resources.h" 19 #include "ui/accessibility/ax_view_state.h" 20 #include "ui/base/l10n/l10n_util.h" 21 #include "ui/base/resource/resource_bundle.h" 22 #include "ui/views/controls/button/label_button.h" 23 #include "ui/views/controls/image_view.h" 24 #include "ui/views/controls/label.h" 25 #include "ui/views/layout/grid_layout.h" 26 #include "ui/views/layout/layout_constants.h" 27 #include "ui/views/widget/widget.h" 28 29 using base::UserMetricsAction; 30 31 namespace { 32 33 // Layout constants. 34 const int kInsetBottomRight = 13; 35 const int kInsetLeft = 14; 36 const int kInsetTop = 9; 37 const int kHeadlineMessagePadding = 4; 38 const int kMessageBubblePadding = 11; 39 40 // How often to show this bubble. 41 const int kShowConflictingModuleBubbleMax = 3; 42 43 } // namespace 44 45 //////////////////////////////////////////////////////////////////////////////// 46 // ConflictingModuleView 47 48 ConflictingModuleView::ConflictingModuleView( 49 views::View* anchor_view, 50 Browser* browser, 51 const GURL& help_center_url) 52 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT), 53 browser_(browser), 54 explanation_(NULL), 55 learn_more_button_(NULL), 56 not_now_button_(NULL), 57 help_center_url_(help_center_url) { 58 set_close_on_deactivate(false); 59 set_close_on_esc(true); 60 61 // Compensate for built-in vertical padding in the anchor view's image. 62 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); 63 64 registrar_.Add(this, chrome::NOTIFICATION_MODULE_INCOMPATIBILITY_BADGE_CHANGE, 65 content::NotificationService::AllSources()); 66 } 67 68 // static 69 void ConflictingModuleView::MaybeShow(Browser* browser, 70 views::View* anchor_view) { 71 static bool done_checking = false; 72 if (done_checking) 73 return; // Only show the bubble once per launch. 74 75 EnumerateModulesModel* model = EnumerateModulesModel::GetInstance(); 76 GURL url = model->GetFirstNotableConflict(); 77 if (!url.is_valid()) { 78 done_checking = true; 79 return; 80 } 81 82 // A pref that counts how often the Sideload Wipeout bubble has been shown. 83 IntegerPrefMember bubble_shown; 84 bubble_shown.Init(prefs::kModuleConflictBubbleShown, 85 browser->profile()->GetPrefs()); 86 if (bubble_shown.GetValue() >= kShowConflictingModuleBubbleMax) { 87 done_checking = true; 88 return; 89 } 90 91 // |anchor_view| must be in a widget (the browser's widget). If not, |browser| 92 // may be destroyed before us, and we'll crash trying to access |browser| 93 // later on. We can't DCHECK |browser|'s widget here as we may be called from 94 // creation of BrowserWindow, which means browser->window() may return NULL. 95 DCHECK(anchor_view); 96 DCHECK(anchor_view->GetWidget()); 97 98 ConflictingModuleView* bubble_delegate = 99 new ConflictingModuleView(anchor_view, browser, url); 100 views::BubbleDelegateView::CreateBubble(bubble_delegate); 101 bubble_delegate->ShowBubble(); 102 103 done_checking = true; 104 } 105 106 //////////////////////////////////////////////////////////////////////////////// 107 // ConflictingModuleView - private. 108 109 ConflictingModuleView::~ConflictingModuleView() { 110 } 111 112 void ConflictingModuleView::ShowBubble() { 113 GetWidget()->Show(); 114 115 IntegerPrefMember bubble_shown; 116 bubble_shown.Init( 117 prefs::kModuleConflictBubbleShown, 118 browser_->profile()->GetPrefs()); 119 bubble_shown.SetValue(bubble_shown.GetValue() + 1); 120 } 121 122 void ConflictingModuleView::DismissBubble() { 123 GetWidget()->Close(); 124 125 content::RecordAction( 126 UserMetricsAction("ConflictingModuleNotificationDismissed")); 127 } 128 129 void ConflictingModuleView::Init() { 130 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 131 132 views::GridLayout* layout = views::GridLayout::CreatePanel(this); 133 layout->SetInsets(kInsetTop, kInsetLeft, 134 kInsetBottomRight, kInsetBottomRight); 135 SetLayoutManager(layout); 136 137 views::ImageView* icon = new views::ImageView(); 138 icon->SetImage(rb.GetNativeImageNamed(IDR_INPUT_ALERT_MENU).ToImageSkia()); 139 gfx::Size icon_size = icon->GetPreferredSize(); 140 141 const int text_column_set_id = 0; 142 views::ColumnSet* upper_columns = layout->AddColumnSet(text_column_set_id); 143 upper_columns->AddColumn( 144 views::GridLayout::LEADING, views::GridLayout::LEADING, 145 0, views::GridLayout::FIXED, icon_size.width(), icon_size.height()); 146 upper_columns->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 147 upper_columns->AddColumn( 148 views::GridLayout::LEADING, views::GridLayout::LEADING, 149 0, views::GridLayout::USE_PREF, 0, 0); 150 151 layout->StartRowWithPadding( 152 0, text_column_set_id, 0, kHeadlineMessagePadding); 153 layout->AddView(icon); 154 explanation_ = new views::Label(); 155 explanation_->SetMultiLine(true); 156 explanation_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 157 explanation_->SetText(l10n_util::GetStringUTF16( 158 IDS_OPTIONS_CONFLICTING_MODULE)); 159 explanation_->SizeToFit(views::Widget::GetLocalizedContentsWidth( 160 IDS_CONFLICTING_MODULE_BUBBLE_WIDTH_CHARS)); 161 layout->AddView(explanation_); 162 163 const int action_row_column_set_id = 1; 164 views::ColumnSet* bottom_columns = 165 layout->AddColumnSet(action_row_column_set_id); 166 bottom_columns->AddPaddingColumn(1, 0); 167 bottom_columns->AddColumn(views::GridLayout::TRAILING, 168 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0); 169 bottom_columns->AddPaddingColumn(0, views::kRelatedButtonHSpacing); 170 bottom_columns->AddColumn(views::GridLayout::TRAILING, 171 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0); 172 layout->AddPaddingRow(0, 7); 173 174 layout->StartRowWithPadding(0, action_row_column_set_id, 175 0, kMessageBubblePadding); 176 learn_more_button_ = new views::LabelButton(this, 177 l10n_util::GetStringUTF16(IDS_CONFLICTS_LEARN_MORE)); 178 learn_more_button_->SetStyle(views::Button::STYLE_BUTTON); 179 layout->AddView(learn_more_button_); 180 not_now_button_ = new views::LabelButton(this, 181 l10n_util::GetStringUTF16(IDS_CONFLICTS_NOT_NOW)); 182 not_now_button_->SetStyle(views::Button::STYLE_BUTTON); 183 layout->AddView(not_now_button_); 184 185 content::RecordAction( 186 UserMetricsAction("ConflictingModuleNotificationShown")); 187 188 UMA_HISTOGRAM_ENUMERATION("ConflictingModule.UserSelection", 189 EnumerateModulesModel::ACTION_BUBBLE_SHOWN, 190 EnumerateModulesModel::ACTION_BOUNDARY); 191 } 192 193 void ConflictingModuleView::ButtonPressed(views::Button* sender, 194 const ui::Event& event) { 195 if (sender == learn_more_button_) { 196 browser_->OpenURL( 197 content::OpenURLParams(help_center_url_, 198 content::Referrer(), 199 NEW_FOREGROUND_TAB, 200 ui::PAGE_TRANSITION_LINK, 201 false)); 202 203 EnumerateModulesModel* model = EnumerateModulesModel::GetInstance(); 204 model->AcknowledgeConflictNotification(); 205 DismissBubble(); 206 } else if (sender == not_now_button_) { 207 DismissBubble(); 208 } 209 } 210 211 void ConflictingModuleView::GetAccessibleState( 212 ui::AXViewState* state) { 213 state->role = ui::AX_ROLE_ALERT; 214 } 215 216 void ConflictingModuleView::ViewHierarchyChanged( 217 const ViewHierarchyChangedDetails& details) { 218 if (details.is_add && details.child == this) 219 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); 220 } 221 222 void ConflictingModuleView::Observe( 223 int type, 224 const content::NotificationSource& source, 225 const content::NotificationDetails& details) { 226 DCHECK(type == chrome::NOTIFICATION_MODULE_INCOMPATIBILITY_BADGE_CHANGE); 227 EnumerateModulesModel* model = EnumerateModulesModel::GetInstance(); 228 if (!model->ShouldShowConflictWarning()) 229 GetWidget()->Close(); 230 } 231