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/extensions/extension_message_bubble_view.h"
7 #include "base/location.h"
8 #include "base/single_thread_task_runner.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "base/thread_task_runner_handle.h"
11 #include "chrome/browser/extensions/extension_message_bubble_controller.h"
12 #include "chrome/browser/ui/view_ids.h"
13 #include "chrome/grit/locale_settings.h"
14 #include "ui/accessibility/ax_view_state.h"
15 #include "ui/base/resource/resource_bundle.h"
16 #include "ui/views/controls/button/label_button.h"
17 #include "ui/views/controls/label.h"
18 #include "ui/views/controls/link.h"
19 #include "ui/views/layout/grid_layout.h"
20 #include "ui/views/view.h"
21 #include "ui/views/widget/widget.h"
26 const int kExtensionListPadding
= 10;
27 const int kInsetBottomRight
= 13;
28 const int kInsetLeft
= 14;
29 const int kInsetTop
= 9;
30 const int kHeadlineMessagePadding
= 4;
31 const int kHeadlineRowPadding
= 10;
32 const int kMessageBubblePadding
= 11;
34 // How long to wait until showing the bubble (in seconds).
35 const int kBubbleAppearanceWaitTime
= 5;
39 namespace extensions
{
41 ExtensionMessageBubbleView::ExtensionMessageBubbleView(
42 views::View
* anchor_view
,
43 views::BubbleBorder::Arrow arrow_location
,
44 scoped_ptr
<extensions::ExtensionMessageBubbleController
> controller
)
45 : BubbleDelegateView(anchor_view
, arrow_location
),
46 controller_(controller
.Pass()),
47 anchor_view_(anchor_view
),
50 dismiss_button_(NULL
),
54 DCHECK(anchor_view
->GetWidget());
55 set_close_on_deactivate(controller_
->CloseOnDeactivate());
56 set_close_on_esc(true);
58 // Compensate for built-in vertical padding in the anchor view's image.
59 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
62 void ExtensionMessageBubbleView::Show() {
63 // Not showing the bubble right away (during startup) has a few benefits:
64 // We don't have to worry about focus being lost due to the Omnibox (or to
65 // other things that want focus at startup). This allows Esc to work to close
66 // the bubble and also solves the keyboard accessibility problem that comes
67 // with focus being lost (we don't have a good generic mechanism of injecting
68 // bubbles into the focus cycle). Another benefit of delaying the show is
69 // that fade-in works (the fade-in isn't apparent if the the bubble appears at
71 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
72 FROM_HERE
, base::Bind(&ExtensionMessageBubbleView::ShowBubble
,
73 weak_factory_
.GetWeakPtr()),
74 base::TimeDelta::FromSeconds(kBubbleAppearanceWaitTime
));
77 void ExtensionMessageBubbleView::OnWidgetDestroying(views::Widget
* widget
) {
78 // To catch Esc, we monitor destroy message. Unless the link has been clicked,
79 // we assume Dismiss was the action taken.
80 if (!link_clicked_
&& !action_taken_
)
81 controller_
->OnBubbleDismiss();
84 ////////////////////////////////////////////////////////////////////////////////
85 // ExtensionMessageBubbleView - private.
87 ExtensionMessageBubbleView::~ExtensionMessageBubbleView() {}
89 void ExtensionMessageBubbleView::ShowBubble() {
93 void ExtensionMessageBubbleView::Init() {
94 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
96 views::GridLayout
* layout
= views::GridLayout::CreatePanel(this);
97 layout
->SetInsets(kInsetTop
, kInsetLeft
,
98 kInsetBottomRight
, kInsetBottomRight
);
99 SetLayoutManager(layout
);
101 ExtensionMessageBubbleController::Delegate
* delegate
=
102 controller_
->delegate();
104 const int headline_column_set_id
= 0;
105 views::ColumnSet
* top_columns
= layout
->AddColumnSet(headline_column_set_id
);
106 top_columns
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::CENTER
,
107 0, views::GridLayout::USE_PREF
, 0, 0);
108 top_columns
->AddPaddingColumn(1, 0);
109 layout
->StartRow(0, headline_column_set_id
);
111 headline_
= new views::Label(delegate
->GetTitle(),
112 rb
.GetFontList(ui::ResourceBundle::MediumFont
));
113 layout
->AddView(headline_
);
115 layout
->AddPaddingRow(0, kHeadlineRowPadding
);
117 const int text_column_set_id
= 1;
118 views::ColumnSet
* upper_columns
= layout
->AddColumnSet(text_column_set_id
);
119 upper_columns
->AddColumn(
120 views::GridLayout::LEADING
, views::GridLayout::LEADING
,
121 0, views::GridLayout::USE_PREF
, 0, 0);
122 layout
->StartRow(0, text_column_set_id
);
124 views::Label
* message
= new views::Label();
125 message
->SetMultiLine(true);
126 message
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
127 message
->SetText(delegate
->GetMessageBody(
128 anchor_view_
->id() == VIEW_ID_BROWSER_ACTION
,
129 controller_
->GetExtensionIdList().size()));
130 message
->SizeToFit(views::Widget::GetLocalizedContentsWidth(
131 IDS_EXTENSION_WIPEOUT_BUBBLE_WIDTH_CHARS
));
132 layout
->AddView(message
);
134 if (delegate
->ShouldShowExtensionList()) {
135 const int extension_list_column_set_id
= 2;
136 views::ColumnSet
* middle_columns
=
137 layout
->AddColumnSet(extension_list_column_set_id
);
138 middle_columns
->AddPaddingColumn(0, kExtensionListPadding
);
139 middle_columns
->AddColumn(
140 views::GridLayout::LEADING
, views::GridLayout::CENTER
,
141 0, views::GridLayout::USE_PREF
, 0, 0);
143 layout
->StartRowWithPadding(0, extension_list_column_set_id
,
144 0, kHeadlineMessagePadding
);
145 views::Label
* extensions
= new views::Label();
146 extensions
->SetMultiLine(true);
147 extensions
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
149 extensions
->SetText(controller_
->GetExtensionListForDisplay());
150 extensions
->SizeToFit(views::Widget::GetLocalizedContentsWidth(
151 IDS_EXTENSION_WIPEOUT_BUBBLE_WIDTH_CHARS
));
152 layout
->AddView(extensions
);
155 base::string16 action_button
= delegate
->GetActionButtonLabel();
157 const int action_row_column_set_id
= 3;
158 views::ColumnSet
* bottom_columns
=
159 layout
->AddColumnSet(action_row_column_set_id
);
160 bottom_columns
->AddColumn(views::GridLayout::LEADING
,
161 views::GridLayout::CENTER
, 0, views::GridLayout::USE_PREF
, 0, 0);
162 bottom_columns
->AddPaddingColumn(1, 0);
163 bottom_columns
->AddColumn(views::GridLayout::TRAILING
,
164 views::GridLayout::CENTER
, 0, views::GridLayout::USE_PREF
, 0, 0);
165 if (!action_button
.empty()) {
166 bottom_columns
->AddColumn(views::GridLayout::TRAILING
,
167 views::GridLayout::CENTER
, 0, views::GridLayout::USE_PREF
, 0, 0);
169 layout
->StartRowWithPadding(0, action_row_column_set_id
,
170 0, kMessageBubblePadding
);
172 learn_more_
= new views::Link(delegate
->GetLearnMoreLabel());
173 learn_more_
->set_listener(this);
174 layout
->AddView(learn_more_
);
176 if (!action_button
.empty()) {
177 action_button_
= new views::LabelButton(this, action_button
);
178 action_button_
->SetStyle(views::Button::STYLE_BUTTON
);
179 layout
->AddView(action_button_
);
182 dismiss_button_
= new views::LabelButton(this,
183 delegate
->GetDismissButtonLabel());
184 dismiss_button_
->SetStyle(views::Button::STYLE_BUTTON
);
185 layout
->AddView(dismiss_button_
);
188 void ExtensionMessageBubbleView::ButtonPressed(views::Button
* sender
,
189 const ui::Event
& event
) {
190 if (sender
== action_button_
) {
191 action_taken_
= true;
192 controller_
->OnBubbleAction();
194 DCHECK_EQ(dismiss_button_
, sender
);
196 GetWidget()->Close();
199 void ExtensionMessageBubbleView::LinkClicked(views::Link
* source
,
201 DCHECK_EQ(learn_more_
, source
);
202 link_clicked_
= true;
203 controller_
->OnLinkClicked();
204 GetWidget()->Close();
207 void ExtensionMessageBubbleView::GetAccessibleState(
208 ui::AXViewState
* state
) {
209 state
->role
= ui::AX_ROLE_ALERT
;
212 void ExtensionMessageBubbleView::ViewHierarchyChanged(
213 const ViewHierarchyChangedDetails
& details
) {
214 if (details
.is_add
&& details
.child
== this)
215 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT
, true);
218 } // namespace extensions