1 // Copyright (c) 2012 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/speech/speech_recognition_bubble.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/browser_finder.h"
12 #include "chrome/browser/ui/views/frame/browser_view.h"
13 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
14 #include "content/public/browser/resource_context.h"
15 #include "content/public/browser/speech_recognition_manager.h"
16 #include "content/public/browser/web_contents.h"
17 #include "content/public/browser/web_contents_view.h"
18 #include "grit/generated_resources.h"
19 #include "grit/theme_resources.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/views/bubble/bubble_delegate.h"
23 #include "ui/views/controls/button/label_button.h"
24 #include "ui/views/controls/image_view.h"
25 #include "ui/views/controls/label.h"
26 #include "ui/views/controls/link.h"
27 #include "ui/views/controls/link_listener.h"
28 #include "ui/views/layout/layout_constants.h"
30 using content::WebContents
;
34 const int kBubbleHorizMargin
= 6;
35 const int kBubbleVertMargin
= 4;
36 const int kBubbleHeadingVertMargin
= 6;
38 // This is the SpeechRecognitionBubble content and views bubble delegate.
39 class SpeechRecognitionBubbleView
: public views::BubbleDelegateView
,
40 public views::ButtonListener
,
41 public views::LinkListener
{
43 SpeechRecognitionBubbleView(SpeechRecognitionBubbleDelegate
* delegate
,
44 views::View
* anchor_view
,
45 const gfx::Rect
& element_rect
,
46 WebContents
* web_contents
);
48 void UpdateLayout(SpeechRecognitionBubbleBase::DisplayMode mode
,
49 const base::string16
& message_text
,
50 const gfx::ImageSkia
& image
);
51 void SetImage(const gfx::ImageSkia
& image
);
53 // views::BubbleDelegateView methods.
54 virtual void OnWidgetActivationChanged(views::Widget
* widget
,
55 bool active
) OVERRIDE
;
56 virtual gfx::Rect
GetAnchorRect() OVERRIDE
;
57 virtual void Init() OVERRIDE
;
59 // views::ButtonListener methods.
60 virtual void ButtonPressed(views::Button
* source
,
61 const ui::Event
& event
) OVERRIDE
;
63 // views::LinkListener methods.
64 virtual void LinkClicked(views::Link
* source
, int event_flags
) OVERRIDE
;
66 // views::View overrides.
67 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
68 virtual void Layout() OVERRIDE
;
70 void set_notify_delegate_on_activation_change(bool notify
) {
71 notify_delegate_on_activation_change_
= notify
;
75 SpeechRecognitionBubbleDelegate
* delegate_
;
76 gfx::Rect element_rect_
;
77 WebContents
* web_contents_
;
78 bool notify_delegate_on_activation_change_
;
79 views::ImageView
* icon_
;
80 views::Label
* heading_
;
81 views::Label
* message_
;
82 views::LabelButton
* try_again_
;
83 views::LabelButton
* cancel_
;
84 views::Link
* mic_settings_
;
85 SpeechRecognitionBubbleBase::DisplayMode display_mode_
;
86 const int kIconLayoutMinWidth
;
88 DISALLOW_COPY_AND_ASSIGN(SpeechRecognitionBubbleView
);
91 SpeechRecognitionBubbleView::SpeechRecognitionBubbleView(
92 SpeechRecognitionBubbleDelegate
* delegate
,
93 views::View
* anchor_view
,
94 const gfx::Rect
& element_rect
,
95 WebContents
* web_contents
)
96 : BubbleDelegateView(anchor_view
, views::BubbleBorder::TOP_LEFT
),
98 element_rect_(element_rect
),
99 web_contents_(web_contents
),
100 notify_delegate_on_activation_change_(true),
107 display_mode_(SpeechRecognitionBubbleBase::DISPLAY_MODE_WARM_UP
),
108 kIconLayoutMinWidth(ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
109 IDR_SPEECH_INPUT_MIC_EMPTY
)->width()) {
110 // The bubble lifetime is managed by its controller; closing on escape or
111 // explicitly closing on deactivation will cause unexpected behavior.
112 set_close_on_esc(false);
113 set_close_on_deactivate(false);
116 void SpeechRecognitionBubbleView::OnWidgetActivationChanged(
117 views::Widget
* widget
, bool active
) {
118 if (widget
== GetWidget() && !active
&& notify_delegate_on_activation_change_
)
119 delegate_
->InfoBubbleFocusChanged();
120 BubbleDelegateView::OnWidgetActivationChanged(widget
, active
);
123 gfx::Rect
SpeechRecognitionBubbleView::GetAnchorRect() {
124 gfx::Rect container_rect
;
125 web_contents_
->GetView()->GetContainerBounds(&container_rect
);
126 gfx::Rect
anchor(element_rect_
);
127 anchor
.Offset(container_rect
.OffsetFromOrigin());
128 if (!container_rect
.Intersects(anchor
))
129 return BubbleDelegateView::GetAnchorRect();
133 void SpeechRecognitionBubbleView::Init() {
134 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
135 const gfx::FontList
& font_list
=
136 rb
.GetFontList(ui::ResourceBundle::MediumFont
);
138 heading_
= new views::Label(
139 l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_BUBBLE_HEADING
), font_list
);
140 heading_
->set_border(views::Border::CreateEmptyBorder(
141 kBubbleHeadingVertMargin
, 0, kBubbleHeadingVertMargin
, 0));
142 heading_
->SetHorizontalAlignment(gfx::ALIGN_CENTER
);
143 AddChildView(heading_
);
145 message_
= new views::Label(base::string16(), font_list
);
146 message_
->SetMultiLine(true);
147 AddChildView(message_
);
149 icon_
= new views::ImageView();
150 icon_
->SetHorizontalAlignment(views::ImageView::CENTER
);
153 cancel_
= new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_CANCEL
));
154 cancel_
->SetStyle(views::Button::STYLE_BUTTON
);
155 AddChildView(cancel_
);
157 try_again_
= new views::LabelButton(
158 this, l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_TRY_AGAIN
));
159 try_again_
->SetStyle(views::Button::STYLE_BUTTON
);
160 AddChildView(try_again_
);
162 mic_settings_
= new views::Link(
163 l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_MIC_SETTINGS
));
164 mic_settings_
->set_listener(this);
165 AddChildView(mic_settings_
);
168 void SpeechRecognitionBubbleView::UpdateLayout(
169 SpeechRecognitionBubbleBase::DisplayMode mode
,
170 const base::string16
& message_text
,
171 const gfx::ImageSkia
& image
) {
172 display_mode_
= mode
;
173 bool is_message
= (mode
== SpeechRecognitionBubbleBase::DISPLAY_MODE_MESSAGE
);
174 icon_
->SetVisible(!is_message
);
175 message_
->SetVisible(is_message
);
176 mic_settings_
->SetVisible(is_message
);
177 try_again_
->SetVisible(is_message
);
179 mode
!= SpeechRecognitionBubbleBase::DISPLAY_MODE_WARM_UP
);
180 heading_
->SetVisible(
181 mode
== SpeechRecognitionBubbleBase::DISPLAY_MODE_RECORDING
);
183 // Clickable elements should be enabled if and only if they are visible.
184 mic_settings_
->SetEnabled(mic_settings_
->visible());
185 try_again_
->SetEnabled(try_again_
->visible());
186 cancel_
->SetEnabled(cancel_
->visible());
189 message_
->SetText(message_text
);
194 if (icon_
->visible())
195 icon_
->ResetImageSize();
197 // When moving from warming up to recording state, the size of the content
198 // stays the same. So we wouldn't get a resize/layout call from the view
199 // system and we do it ourselves.
200 if (GetPreferredSize() == size()) // |size()| here is the current size.
206 void SpeechRecognitionBubbleView::SetImage(const gfx::ImageSkia
& image
) {
207 icon_
->SetImage(image
);
210 void SpeechRecognitionBubbleView::ButtonPressed(views::Button
* source
,
211 const ui::Event
& event
) {
212 if (source
== cancel_
) {
213 delegate_
->InfoBubbleButtonClicked(SpeechRecognitionBubble::BUTTON_CANCEL
);
214 } else if (source
== try_again_
) {
215 delegate_
->InfoBubbleButtonClicked(
216 SpeechRecognitionBubble::BUTTON_TRY_AGAIN
);
218 NOTREACHED() << "Unknown button";
222 void SpeechRecognitionBubbleView::LinkClicked(views::Link
* source
,
224 DCHECK_EQ(mic_settings_
, source
);
225 content::SpeechRecognitionManager::GetInstance()->ShowAudioInputSettings();
228 gfx::Size
SpeechRecognitionBubbleView::GetPreferredSize() {
229 int width
= heading_
->GetPreferredSize().width();
230 int control_width
= cancel_
->GetPreferredSize().width();
231 if (try_again_
->visible()) {
232 control_width
+= try_again_
->GetPreferredSize().width() +
233 views::kRelatedButtonHSpacing
;
235 width
= std::max(width
, control_width
);
236 control_width
= std::max(icon_
->GetPreferredSize().width(),
237 kIconLayoutMinWidth
);
238 width
= std::max(width
, control_width
);
239 if (mic_settings_
->visible()) {
240 control_width
= mic_settings_
->GetPreferredSize().width();
241 width
= std::max(width
, control_width
);
244 int height
= cancel_
->GetPreferredSize().height();
245 if (message_
->visible()) {
246 height
+= message_
->GetHeightForWidth(width
) +
247 views::kLabelToControlVerticalSpacing
;
249 if (heading_
->visible())
250 height
+= heading_
->GetPreferredSize().height();
251 if (icon_
->visible())
252 height
+= icon_
->GetImage().height();
253 if (mic_settings_
->visible())
254 height
+= mic_settings_
->GetPreferredSize().height();
255 width
+= kBubbleHorizMargin
* 2;
256 height
+= kBubbleVertMargin
* 2;
258 return gfx::Size(width
, height
);
261 void SpeechRecognitionBubbleView::Layout() {
262 int x
= kBubbleHorizMargin
;
263 int y
= kBubbleVertMargin
;
264 int available_width
= width() - kBubbleHorizMargin
* 2;
265 int available_height
= height() - kBubbleVertMargin
* 2;
267 if (message_
->visible()) {
268 DCHECK(try_again_
->visible());
270 int control_height
= try_again_
->GetPreferredSize().height();
271 int try_again_width
= try_again_
->GetPreferredSize().width();
272 int cancel_width
= cancel_
->GetPreferredSize().width();
273 y
+= available_height
- control_height
;
274 x
+= (available_width
- cancel_width
- try_again_width
-
275 views::kRelatedButtonHSpacing
) / 2;
276 try_again_
->SetBounds(x
, y
, try_again_width
, control_height
);
277 cancel_
->SetBounds(x
+ try_again_width
+ views::kRelatedButtonHSpacing
, y
,
278 cancel_width
, control_height
);
280 control_height
= message_
->GetHeightForWidth(available_width
);
281 message_
->SetBounds(kBubbleHorizMargin
, kBubbleVertMargin
,
282 available_width
, control_height
);
283 y
= kBubbleVertMargin
+ control_height
;
285 control_height
= mic_settings_
->GetPreferredSize().height();
286 mic_settings_
->SetBounds(kBubbleHorizMargin
, y
, available_width
,
289 DCHECK(icon_
->visible());
291 int control_height
= icon_
->GetImage().height();
292 if (display_mode_
== SpeechRecognitionBubbleBase::DISPLAY_MODE_WARM_UP
)
293 y
= (available_height
- control_height
) / 2;
294 icon_
->SetBounds(x
, y
, available_width
, control_height
);
297 if (heading_
->visible()) {
298 control_height
= heading_
->GetPreferredSize().height();
299 heading_
->SetBounds(x
, y
, available_width
, control_height
);
303 if (cancel_
->visible()) {
304 control_height
= cancel_
->GetPreferredSize().height();
305 int width
= cancel_
->GetPreferredSize().width();
306 cancel_
->SetBounds(x
+ (available_width
- width
) / 2, y
, width
,
312 // Implementation of SpeechRecognitionBubble.
313 class SpeechRecognitionBubbleImpl
: public SpeechRecognitionBubbleBase
{
315 SpeechRecognitionBubbleImpl(WebContents
* web_contents
,
317 const gfx::Rect
& element_rect
);
318 virtual ~SpeechRecognitionBubbleImpl();
320 // SpeechRecognitionBubble methods.
321 virtual void Show() OVERRIDE
;
322 virtual void Hide() OVERRIDE
;
324 // SpeechRecognitionBubbleBase methods.
325 virtual void UpdateLayout() OVERRIDE
;
326 virtual void UpdateImage() OVERRIDE
;
330 SpeechRecognitionBubbleView
* bubble_
;
331 gfx::Rect element_rect_
;
333 DISALLOW_COPY_AND_ASSIGN(SpeechRecognitionBubbleImpl
);
336 SpeechRecognitionBubbleImpl::SpeechRecognitionBubbleImpl(
337 WebContents
* web_contents
, Delegate
* delegate
,
338 const gfx::Rect
& element_rect
)
339 : SpeechRecognitionBubbleBase(web_contents
),
342 element_rect_(element_rect
) {
345 SpeechRecognitionBubbleImpl::~SpeechRecognitionBubbleImpl() {
347 bubble_
->set_notify_delegate_on_activation_change(false);
348 bubble_
->GetWidget()->Close();
352 void SpeechRecognitionBubbleImpl::Show() {
354 views::View
* icon
= NULL
;
356 // Anchor to the location bar, in case |element_rect| is offscreen.
357 WebContents
* web_contents
= GetWebContents();
358 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents
);
360 BrowserView
* browser_view
=
361 BrowserView::GetBrowserViewForBrowser(browser
);
362 icon
= browser_view
->GetLocationBarView() ?
363 browser_view
->GetLocationBarView()->GetLocationBarAnchor() : NULL
;
366 bubble_
= new SpeechRecognitionBubbleView(delegate_
, icon
, element_rect_
,
370 // We dont't have an icon to attach to. Manually specify the web contents
371 // window as the parent.
372 bubble_
->set_parent_window(
373 web_contents
->GetView()->GetTopLevelNativeWindow());
376 views::BubbleDelegateView::CreateBubble(bubble_
);
379 bubble_
->GetWidget()->Show();
382 void SpeechRecognitionBubbleImpl::Hide() {
384 bubble_
->GetWidget()->Hide();
387 void SpeechRecognitionBubbleImpl::UpdateLayout() {
389 bubble_
->UpdateLayout(display_mode(), message_text(), icon_image());
392 void SpeechRecognitionBubbleImpl::UpdateImage() {
394 bubble_
->SetImage(icon_image());
399 SpeechRecognitionBubble
* SpeechRecognitionBubble::CreateNativeBubble(
400 WebContents
* web_contents
,
401 SpeechRecognitionBubble::Delegate
* delegate
,
402 const gfx::Rect
& element_rect
) {
403 return new SpeechRecognitionBubbleImpl(web_contents
, delegate
, element_rect
);