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.
5 #include "chrome/browser/ui/views/conflicting_module_view_win.h"
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"
29 using base::UserMetricsAction
;
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;
40 // How often to show this bubble.
41 const int kShowConflictingModuleBubbleMax
= 3;
45 ////////////////////////////////////////////////////////////////////////////////
46 // ConflictingModuleView
48 ConflictingModuleView::ConflictingModuleView(
49 views::View
* anchor_view
,
51 const GURL
& help_center_url
)
52 : BubbleDelegateView(anchor_view
, views::BubbleBorder::TOP_RIGHT
),
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);
61 // Compensate for built-in vertical padding in the anchor view's image.
62 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
64 registrar_
.Add(this, chrome::NOTIFICATION_MODULE_INCOMPATIBILITY_BADGE_CHANGE
,
65 content::NotificationService::AllSources());
69 void ConflictingModuleView::MaybeShow(Browser
* browser
,
70 views::View
* anchor_view
) {
71 static bool done_checking
= false;
73 return; // Only show the bubble once per launch.
75 EnumerateModulesModel
* model
= EnumerateModulesModel::GetInstance();
76 GURL url
= model
->GetFirstNotableConflict();
77 if (!url
.is_valid()) {
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
) {
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.
96 DCHECK(anchor_view
->GetWidget());
98 ConflictingModuleView
* bubble_delegate
=
99 new ConflictingModuleView(anchor_view
, browser
, url
);
100 views::BubbleDelegateView::CreateBubble(bubble_delegate
);
101 bubble_delegate
->ShowBubble();
103 done_checking
= true;
106 ////////////////////////////////////////////////////////////////////////////////
107 // ConflictingModuleView - private.
109 ConflictingModuleView::~ConflictingModuleView() {
112 void ConflictingModuleView::ShowBubble() {
115 IntegerPrefMember bubble_shown
;
117 prefs::kModuleConflictBubbleShown
,
118 browser_
->profile()->GetPrefs());
119 bubble_shown
.SetValue(bubble_shown
.GetValue() + 1);
122 void ConflictingModuleView::DismissBubble() {
123 GetWidget()->Close();
125 content::RecordAction(
126 UserMetricsAction("ConflictingModuleNotificationDismissed"));
129 void ConflictingModuleView::Init() {
130 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
132 views::GridLayout
* layout
= views::GridLayout::CreatePanel(this);
133 layout
->SetInsets(kInsetTop
, kInsetLeft
,
134 kInsetBottomRight
, kInsetBottomRight
);
135 SetLayoutManager(layout
);
137 views::ImageView
* icon
= new views::ImageView();
138 icon
->SetImage(rb
.GetNativeImageNamed(IDR_INPUT_ALERT_MENU
).ToImageSkia());
139 gfx::Size icon_size
= icon
->GetPreferredSize();
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);
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_
);
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);
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_
);
185 content::RecordAction(
186 UserMetricsAction("ConflictingModuleNotificationShown"));
188 UMA_HISTOGRAM_ENUMERATION("ConflictingModule.UserSelection",
189 EnumerateModulesModel::ACTION_BUBBLE_SHOWN
,
190 EnumerateModulesModel::ACTION_BOUNDARY
);
193 void ConflictingModuleView::ButtonPressed(views::Button
* sender
,
194 const ui::Event
& event
) {
195 if (sender
== learn_more_button_
) {
197 content::OpenURLParams(help_center_url_
,
200 ui::PAGE_TRANSITION_LINK
,
203 EnumerateModulesModel
* model
= EnumerateModulesModel::GetInstance();
204 model
->AcknowledgeConflictNotification();
206 } else if (sender
== not_now_button_
) {
211 void ConflictingModuleView::GetAccessibleState(
212 ui::AXViewState
* state
) {
213 state
->role
= ui::AX_ROLE_ALERT
;
216 void ConflictingModuleView::ViewHierarchyChanged(
217 const ViewHierarchyChangedDetails
& details
) {
218 if (details
.is_add
&& details
.child
== this)
219 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT
, true);
222 void ConflictingModuleView::Observe(
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();