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