• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/speech/speech_input_bubble.h"
6 
7 #include <algorithm>
8 
9 #include "base/message_loop.h"
10 #include "base/utf_string_conversions.h"
11 #include "chrome/browser/ui/browser_window.h"
12 #include "chrome/browser/ui/views/bubble/bubble.h"
13 #include "content/browser/tab_contents/tab_contents.h"
14 #include "content/browser/tab_contents/tab_contents_view.h"
15 #include "grit/generated_resources.h"
16 #include "grit/theme_resources.h"
17 #include "media/audio/audio_manager.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/gfx/canvas.h"
21 #include "views/border.h"
22 #include "views/controls/button/native_button.h"
23 #include "views/controls/image_view.h"
24 #include "views/controls/label.h"
25 #include "views/controls/link.h"
26 #include "views/layout/layout_constants.h"
27 #include "views/view.h"
28 
29 namespace {
30 
31 const int kBubbleHorizMargin = 6;
32 const int kBubbleVertMargin = 4;
33 const int kBubbleHeadingVertMargin = 6;
34 
35 // This is the content view which is placed inside a SpeechInputBubble.
36 class ContentView
37     : public views::View,
38       public views::ButtonListener,
39       public views::LinkController {
40  public:
41   explicit ContentView(SpeechInputBubbleDelegate* delegate);
42 
43   void UpdateLayout(SpeechInputBubbleBase::DisplayMode mode,
44                     const string16& message_text,
45                     const SkBitmap& image);
46   void SetImage(const SkBitmap& image);
47 
48   // views::ButtonListener methods.
49   virtual void ButtonPressed(views::Button* source, const views::Event& event);
50 
51   // views::LinkController methods.
52   virtual void LinkActivated(views::Link* source, int event_flags);
53 
54   // views::View overrides.
55   virtual gfx::Size GetPreferredSize();
56   virtual void Layout();
57 
58  private:
59   SpeechInputBubbleDelegate* delegate_;
60   views::ImageView* icon_;
61   views::Label* heading_;
62   views::Label* message_;
63   views::NativeButton* try_again_;
64   views::NativeButton* cancel_;
65   views::Link* mic_settings_;
66   SpeechInputBubbleBase::DisplayMode display_mode_;
67   const int kIconLayoutMinWidth;
68 
69   DISALLOW_COPY_AND_ASSIGN(ContentView);
70 };
71 
ContentView(SpeechInputBubbleDelegate * delegate)72 ContentView::ContentView(SpeechInputBubbleDelegate* delegate)
73      : delegate_(delegate),
74        display_mode_(SpeechInputBubbleBase::DISPLAY_MODE_WARM_UP),
75        kIconLayoutMinWidth(ResourceBundle::GetSharedInstance().GetBitmapNamed(
76                            IDR_SPEECH_INPUT_MIC_EMPTY)->width()) {
77   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
78   const gfx::Font& font = rb.GetFont(ResourceBundle::MediumFont);
79 
80   heading_ = new views::Label(
81       UTF16ToWide(l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_BUBBLE_HEADING)));
82   heading_->set_border(views::Border::CreateEmptyBorder(
83       kBubbleHeadingVertMargin, 0, kBubbleHeadingVertMargin, 0));
84   heading_->SetFont(font);
85   heading_->SetHorizontalAlignment(views::Label::ALIGN_CENTER);
86   heading_->SetText(UTF16ToWide(
87       l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_BUBBLE_HEADING)));
88   AddChildView(heading_);
89 
90   message_ = new views::Label();
91   message_->SetFont(font);
92   message_->SetHorizontalAlignment(views::Label::ALIGN_CENTER);
93   message_->SetMultiLine(true);
94   AddChildView(message_);
95 
96   icon_ = new views::ImageView();
97   icon_->SetHorizontalAlignment(views::ImageView::CENTER);
98   AddChildView(icon_);
99 
100   cancel_ = new views::NativeButton(
101       this,
102       UTF16ToWide(l10n_util::GetStringUTF16(IDS_CANCEL)));
103   AddChildView(cancel_);
104 
105   try_again_ = new views::NativeButton(
106       this,
107       UTF16ToWide(l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_TRY_AGAIN)));
108   AddChildView(try_again_);
109 
110   mic_settings_ = new views::Link(
111       UTF16ToWide(l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_MIC_SETTINGS)));
112   mic_settings_->SetController(this);
113   AddChildView(mic_settings_);
114 }
115 
UpdateLayout(SpeechInputBubbleBase::DisplayMode mode,const string16 & message_text,const SkBitmap & image)116 void ContentView::UpdateLayout(SpeechInputBubbleBase::DisplayMode mode,
117                                const string16& message_text,
118                                const SkBitmap& image) {
119   display_mode_ = mode;
120   bool is_message = (mode == SpeechInputBubbleBase::DISPLAY_MODE_MESSAGE);
121   icon_->SetVisible(!is_message);
122   message_->SetVisible(is_message);
123   mic_settings_->SetVisible(is_message);
124   try_again_->SetVisible(is_message);
125   cancel_->SetVisible(mode != SpeechInputBubbleBase::DISPLAY_MODE_WARM_UP);
126   heading_->SetVisible(mode == SpeechInputBubbleBase::DISPLAY_MODE_RECORDING);
127 
128   if (is_message) {
129     message_->SetText(UTF16ToWideHack(message_text));
130   } else {
131     SetImage(image);
132   }
133 
134   if (icon_->IsVisible())
135     icon_->ResetImageSize();
136 
137   // When moving from warming up to recording state, the size of the content
138   // stays the same. So we wouldn't get a resize/layout call from the view
139   // system and we do it ourselves.
140   if (GetPreferredSize() == size())  // |size()| here is the current size.
141     Layout();
142 }
143 
SetImage(const SkBitmap & image)144 void ContentView::SetImage(const SkBitmap& image) {
145   icon_->SetImage(image);
146 }
147 
ButtonPressed(views::Button * source,const views::Event & event)148 void ContentView::ButtonPressed(views::Button* source,
149                                 const views::Event& event) {
150   if (source == cancel_) {
151     delegate_->InfoBubbleButtonClicked(SpeechInputBubble::BUTTON_CANCEL);
152   } else if (source == try_again_) {
153     delegate_->InfoBubbleButtonClicked(SpeechInputBubble::BUTTON_TRY_AGAIN);
154   } else {
155     NOTREACHED() << "Unknown button";
156   }
157 }
158 
LinkActivated(views::Link * source,int event_flags)159 void ContentView::LinkActivated(views::Link* source, int event_flags) {
160   DCHECK_EQ(source, mic_settings_);
161   AudioManager::GetAudioManager()->ShowAudioInputSettings();
162 }
163 
GetPreferredSize()164 gfx::Size ContentView::GetPreferredSize() {
165   int width = heading_->GetPreferredSize().width();
166   int control_width = cancel_->GetPreferredSize().width();
167   if (try_again_->IsVisible()) {
168     control_width += try_again_->GetPreferredSize().width() +
169                      views::kRelatedButtonHSpacing;
170   }
171   width = std::max(width, control_width);
172   control_width = std::max(icon_->GetPreferredSize().width(),
173                            kIconLayoutMinWidth);
174   width = std::max(width, control_width);
175   if (mic_settings_->IsVisible()) {
176     control_width = mic_settings_->GetPreferredSize().width();
177     width = std::max(width, control_width);
178   }
179 
180   int height = cancel_->GetPreferredSize().height();
181   if (message_->IsVisible()) {
182     height += message_->GetHeightForWidth(width) +
183               views::kLabelToControlVerticalSpacing;
184   }
185   if (heading_->IsVisible())
186     height += heading_->GetPreferredSize().height();
187   if (icon_->IsVisible())
188     height += icon_->GetImage().height();
189   if (mic_settings_->IsVisible())
190     height += mic_settings_->GetPreferredSize().height();
191   width += kBubbleHorizMargin * 2;
192   height += kBubbleVertMargin * 2;
193 
194   return gfx::Size(width, height);
195 }
196 
Layout()197 void ContentView::Layout() {
198   int x = kBubbleHorizMargin;
199   int y = kBubbleVertMargin;
200   int available_width = width() - kBubbleHorizMargin * 2;
201   int available_height = height() - kBubbleVertMargin * 2;
202 
203   if (message_->IsVisible()) {
204     DCHECK(try_again_->IsVisible());
205 
206     int control_height = try_again_->GetPreferredSize().height();
207     int try_again_width = try_again_->GetPreferredSize().width();
208     int cancel_width = cancel_->GetPreferredSize().width();
209     y += available_height - control_height;
210     x += (available_width - cancel_width - try_again_width -
211           views::kRelatedButtonHSpacing) / 2;
212     try_again_->SetBounds(x, y, try_again_width, control_height);
213     cancel_->SetBounds(x + try_again_width + views::kRelatedButtonHSpacing, y,
214                        cancel_width, control_height);
215 
216     control_height = message_->GetHeightForWidth(available_width);
217     message_->SetBounds(kBubbleHorizMargin, kBubbleVertMargin,
218                         available_width, control_height);
219     y = kBubbleVertMargin + control_height;
220 
221     control_height = mic_settings_->GetPreferredSize().height();
222     mic_settings_->SetBounds(kBubbleHorizMargin, y, available_width,
223                              control_height);
224   } else {
225     DCHECK(icon_->IsVisible());
226 
227     int control_height = icon_->GetImage().height();
228     if (display_mode_ == SpeechInputBubbleBase::DISPLAY_MODE_WARM_UP)
229       y = (available_height - control_height) / 2;
230     icon_->SetBounds(x, y, available_width, control_height);
231     y += control_height;
232 
233     if (heading_->IsVisible()) {
234       control_height = heading_->GetPreferredSize().height();
235       heading_->SetBounds(x, y, available_width, control_height);
236       y += control_height;
237     }
238 
239     if (cancel_->IsVisible()) {
240       control_height = cancel_->GetPreferredSize().height();
241       int width = cancel_->GetPreferredSize().width();
242       cancel_->SetBounds(x + (available_width - width) / 2, y, width,
243                          control_height);
244     }
245   }
246 }
247 
248 // Implementation of SpeechInputBubble.
249 class SpeechInputBubbleImpl
250     : public SpeechInputBubbleBase,
251       public BubbleDelegate {
252  public:
253   SpeechInputBubbleImpl(TabContents* tab_contents,
254                         Delegate* delegate,
255                         const gfx::Rect& element_rect);
256   virtual ~SpeechInputBubbleImpl();
257 
258   // SpeechInputBubble methods.
259   virtual void Show();
260   virtual void Hide();
261 
262   // SpeechInputBubbleBase methods.
263   virtual void UpdateLayout();
264   virtual void UpdateImage();
265 
266   // Returns the screen rectangle to use as the info bubble's target.
267   // |element_rect| is the html element's bounds in page coordinates.
268   gfx::Rect GetInfoBubbleTarget(const gfx::Rect& element_rect);
269 
270   // BubbleDelegate
271   virtual void BubbleClosing(Bubble* bubble, bool closed_by_escape);
272   virtual bool CloseOnEscape();
273   virtual bool FadeInOnShow();
274 
275  private:
276   Delegate* delegate_;
277   Bubble* bubble_;
278   ContentView* bubble_content_;
279   gfx::Rect element_rect_;
280 
281   // Set to true if the object is being destroyed normally instead of the
282   // user clicking outside the window causing it to close automatically.
283   bool did_invoke_close_;
284 
285   DISALLOW_COPY_AND_ASSIGN(SpeechInputBubbleImpl);
286 };
287 
SpeechInputBubbleImpl(TabContents * tab_contents,Delegate * delegate,const gfx::Rect & element_rect)288 SpeechInputBubbleImpl::SpeechInputBubbleImpl(TabContents* tab_contents,
289                                              Delegate* delegate,
290                                              const gfx::Rect& element_rect)
291     : SpeechInputBubbleBase(tab_contents),
292       delegate_(delegate),
293       bubble_(NULL),
294       bubble_content_(NULL),
295       element_rect_(element_rect),
296       did_invoke_close_(false) {
297 }
298 
~SpeechInputBubbleImpl()299 SpeechInputBubbleImpl::~SpeechInputBubbleImpl() {
300   did_invoke_close_ = true;
301   Hide();
302 }
303 
GetInfoBubbleTarget(const gfx::Rect & element_rect)304 gfx::Rect SpeechInputBubbleImpl::GetInfoBubbleTarget(
305     const gfx::Rect& element_rect) {
306   gfx::Rect container_rect;
307   tab_contents()->GetContainerBounds(&container_rect);
308   return gfx::Rect(
309       container_rect.x() + element_rect.x() + element_rect.width() -
310           kBubbleTargetOffsetX,
311       container_rect.y() + element_rect.y() + element_rect.height(), 1, 1);
312 }
313 
BubbleClosing(Bubble * bubble,bool closed_by_escape)314 void SpeechInputBubbleImpl::BubbleClosing(Bubble* bubble,
315                                           bool closed_by_escape) {
316   bubble_ = NULL;
317   bubble_content_ = NULL;
318   if (!did_invoke_close_)
319     delegate_->InfoBubbleFocusChanged();
320 }
321 
CloseOnEscape()322 bool SpeechInputBubbleImpl::CloseOnEscape() {
323   return false;
324 }
325 
FadeInOnShow()326 bool SpeechInputBubbleImpl::FadeInOnShow() {
327   return false;
328 }
329 
Show()330 void SpeechInputBubbleImpl::Show() {
331   if (bubble_)
332     return;  // nothing to do, already visible.
333 
334   bubble_content_ = new ContentView(delegate_);
335   UpdateLayout();
336 
337   views::NativeWidget* toplevel_widget =
338       views::NativeWidget::GetTopLevelNativeWidget(
339           tab_contents()->view()->GetNativeView());
340   if (toplevel_widget) {
341     bubble_ = Bubble::Show(toplevel_widget->GetWidget(),
342                            GetInfoBubbleTarget(element_rect_),
343                            BubbleBorder::TOP_LEFT, bubble_content_,
344                            this);
345 
346     // We don't want fade outs when closing because it makes speech recognition
347     // appear slower than it is. Also setting it to false allows |Close| to
348     // destroy the bubble immediately instead of waiting for the fade animation
349     // to end so the caller can manage this object's life cycle like a normal
350     // stack based or member variable object.
351     bubble_->set_fade_away_on_close(false);
352   }
353 }
354 
Hide()355 void SpeechInputBubbleImpl::Hide() {
356   if (bubble_)
357     bubble_->Close();
358 }
359 
UpdateLayout()360 void SpeechInputBubbleImpl::UpdateLayout() {
361   if (bubble_content_)
362     bubble_content_->UpdateLayout(display_mode(), message_text(), icon_image());
363   if (bubble_)  // Will be null on first call.
364     bubble_->SizeToContents();
365 }
366 
UpdateImage()367 void SpeechInputBubbleImpl::UpdateImage() {
368   if (bubble_content_)
369     bubble_content_->SetImage(icon_image());
370 }
371 
372 }  // namespace
373 
CreateNativeBubble(TabContents * tab_contents,SpeechInputBubble::Delegate * delegate,const gfx::Rect & element_rect)374 SpeechInputBubble* SpeechInputBubble::CreateNativeBubble(
375     TabContents* tab_contents,
376     SpeechInputBubble::Delegate* delegate,
377     const gfx::Rect& element_rect) {
378   return new SpeechInputBubbleImpl(tab_contents, delegate, element_rect);
379 }
380