| OLD | NEW | 
|---|
|  | (Empty) | 
| 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. |  | 
| 4 |  | 
| 5 #include "chrome/browser/speech/speech_input_bubble.h" |  | 
| 6 |  | 
| 7 #include <algorithm> |  | 
| 8 |  | 
| 9 #include "base/utf_string_conversions.h" |  | 
| 10 #include "chrome/browser/ui/browser.h" |  | 
| 11 #include "chrome/browser/ui/views/frame/browser_view.h" |  | 
| 12 #include "chrome/browser/ui/views/location_bar/location_icon_view.h" |  | 
| 13 #include "chrome/browser/ui/views/toolbar_view.h" |  | 
| 14 #include "chrome/browser/ui/views/window.h" |  | 
| 15 #include "content/public/browser/resource_context.h" |  | 
| 16 #include "content/public/browser/speech_input_manager.h" |  | 
| 17 #include "content/public/browser/web_contents.h" |  | 
| 18 #include "content/public/browser/web_contents_view.h" |  | 
| 19 #include "grit/generated_resources.h" |  | 
| 20 #include "grit/theme_resources.h" |  | 
| 21 #include "ui/base/l10n/l10n_util.h" |  | 
| 22 #include "ui/base/resource/resource_bundle.h" |  | 
| 23 #include "ui/views/bubble/bubble_delegate.h" |  | 
| 24 #include "ui/views/controls/button/text_button.h" |  | 
| 25 #include "ui/views/controls/image_view.h" |  | 
| 26 #include "ui/views/controls/label.h" |  | 
| 27 #include "ui/views/controls/link.h" |  | 
| 28 #include "ui/views/controls/link_listener.h" |  | 
| 29 #include "ui/views/layout/layout_constants.h" |  | 
| 30 |  | 
| 31 using content::WebContents; |  | 
| 32 |  | 
| 33 namespace { |  | 
| 34 |  | 
| 35 const int kBubbleHorizMargin = 6; |  | 
| 36 const int kBubbleVertMargin = 4; |  | 
| 37 const int kBubbleHeadingVertMargin = 6; |  | 
| 38 const int kIconHorizontalOffset = 27; |  | 
| 39 const int kIconVerticalOffset = -7; |  | 
| 40 |  | 
| 41 // This is the SpeechInputBubble content and views bubble delegate. |  | 
| 42 class SpeechInputBubbleView |  | 
| 43     : public views::BubbleDelegateView, |  | 
| 44       public views::ButtonListener, |  | 
| 45       public views::LinkListener { |  | 
| 46  public: |  | 
| 47   SpeechInputBubbleView(SpeechInputBubbleDelegate* delegate, |  | 
| 48                         views::View* anchor_view, |  | 
| 49                         const gfx::Rect& element_rect, |  | 
| 50                         WebContents* web_contents); |  | 
| 51 |  | 
| 52   void UpdateLayout(SpeechInputBubbleBase::DisplayMode mode, |  | 
| 53                     const string16& message_text, |  | 
| 54                     const SkBitmap& image); |  | 
| 55   void SetImage(const SkBitmap& image); |  | 
| 56 |  | 
| 57   // views::BubbleDelegateView methods. |  | 
| 58   virtual void OnWidgetActivationChanged(views::Widget* widget, |  | 
| 59                                          bool active) OVERRIDE; |  | 
| 60   virtual gfx::Rect GetAnchorRect() OVERRIDE; |  | 
| 61   virtual void Init() OVERRIDE; |  | 
| 62 |  | 
| 63   // views::ButtonListener methods. |  | 
| 64   virtual void ButtonPressed(views::Button* source, |  | 
| 65                              const views::Event& event) OVERRIDE; |  | 
| 66 |  | 
| 67   // views::LinkListener methods. |  | 
| 68   virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; |  | 
| 69 |  | 
| 70   // views::View overrides. |  | 
| 71   virtual gfx::Size GetPreferredSize() OVERRIDE; |  | 
| 72   virtual void Layout() OVERRIDE; |  | 
| 73 |  | 
| 74   void set_notify_delegate_on_activation_change(bool notify) { |  | 
| 75     notify_delegate_on_activation_change_ = notify; |  | 
| 76   } |  | 
| 77 |  | 
| 78  private: |  | 
| 79   SpeechInputBubbleDelegate* delegate_; |  | 
| 80   gfx::Rect element_rect_; |  | 
| 81   WebContents* web_contents_; |  | 
| 82   bool notify_delegate_on_activation_change_; |  | 
| 83   views::ImageView* icon_; |  | 
| 84   views::Label* heading_; |  | 
| 85   views::Label* message_; |  | 
| 86   views::TextButton* try_again_; |  | 
| 87   views::TextButton* cancel_; |  | 
| 88   views::Link* mic_settings_; |  | 
| 89   SpeechInputBubbleBase::DisplayMode display_mode_; |  | 
| 90   const int kIconLayoutMinWidth; |  | 
| 91 |  | 
| 92   DISALLOW_COPY_AND_ASSIGN(SpeechInputBubbleView); |  | 
| 93 }; |  | 
| 94 |  | 
| 95 SpeechInputBubbleView::SpeechInputBubbleView( |  | 
| 96     SpeechInputBubbleDelegate* delegate, |  | 
| 97     views::View* anchor_view, |  | 
| 98     const gfx::Rect& element_rect, |  | 
| 99     WebContents* web_contents) |  | 
| 100     : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_LEFT), |  | 
| 101       delegate_(delegate), |  | 
| 102       element_rect_(element_rect), |  | 
| 103       web_contents_(web_contents), |  | 
| 104       notify_delegate_on_activation_change_(true), |  | 
| 105       icon_(NULL), |  | 
| 106       heading_(NULL), |  | 
| 107       message_(NULL), |  | 
| 108       try_again_(NULL), |  | 
| 109       cancel_(NULL), |  | 
| 110       mic_settings_(NULL), |  | 
| 111       display_mode_(SpeechInputBubbleBase::DISPLAY_MODE_WARM_UP), |  | 
| 112       kIconLayoutMinWidth(ResourceBundle::GetSharedInstance().GetBitmapNamed( |  | 
| 113                           IDR_SPEECH_INPUT_MIC_EMPTY)->width()) { |  | 
| 114   // The bubble lifetime is managed by its controller; closing on escape or |  | 
| 115   // explicitly closing on deactivation will cause unexpected behavior. |  | 
| 116   set_close_on_esc(false); |  | 
| 117   set_close_on_deactivate(false); |  | 
| 118 } |  | 
| 119 |  | 
| 120 void SpeechInputBubbleView::OnWidgetActivationChanged(views::Widget* widget, |  | 
| 121                                                       bool active) { |  | 
| 122   if (widget == GetWidget() && !active && notify_delegate_on_activation_change_) |  | 
| 123     delegate_->InfoBubbleFocusChanged(); |  | 
| 124   BubbleDelegateView::OnWidgetActivationChanged(widget, active); |  | 
| 125 } |  | 
| 126 |  | 
| 127 gfx::Rect SpeechInputBubbleView::GetAnchorRect() { |  | 
| 128   gfx::Rect container_rect; |  | 
| 129   web_contents_->GetContainerBounds(&container_rect); |  | 
| 130   gfx::Rect anchor(element_rect_); |  | 
| 131   anchor.Offset(container_rect.origin()); |  | 
| 132   if (!container_rect.Intersects(anchor)) |  | 
| 133     return BubbleDelegateView::GetAnchorRect(); |  | 
| 134   return anchor; |  | 
| 135 } |  | 
| 136 |  | 
| 137 void SpeechInputBubbleView::Init() { |  | 
| 138   ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |  | 
| 139   const gfx::Font& font = rb.GetFont(ResourceBundle::MediumFont); |  | 
| 140 |  | 
| 141   heading_ = new views::Label( |  | 
| 142       l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_BUBBLE_HEADING)); |  | 
| 143   heading_->set_border(views::Border::CreateEmptyBorder( |  | 
| 144       kBubbleHeadingVertMargin, 0, kBubbleHeadingVertMargin, 0)); |  | 
| 145   heading_->SetFont(font); |  | 
| 146   heading_->SetHorizontalAlignment(views::Label::ALIGN_CENTER); |  | 
| 147   heading_->SetText( |  | 
| 148       l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_BUBBLE_HEADING)); |  | 
| 149   AddChildView(heading_); |  | 
| 150 |  | 
| 151   message_ = new views::Label(); |  | 
| 152   message_->SetFont(font); |  | 
| 153   message_->SetHorizontalAlignment(views::Label::ALIGN_CENTER); |  | 
| 154   message_->SetMultiLine(true); |  | 
| 155   AddChildView(message_); |  | 
| 156 |  | 
| 157   icon_ = new views::ImageView(); |  | 
| 158   icon_->SetHorizontalAlignment(views::ImageView::CENTER); |  | 
| 159   AddChildView(icon_); |  | 
| 160 |  | 
| 161   cancel_ = new views::NativeTextButton( |  | 
| 162       this, l10n_util::GetStringUTF16(IDS_CANCEL)); |  | 
| 163   AddChildView(cancel_); |  | 
| 164 |  | 
| 165   try_again_ = new views::NativeTextButton( |  | 
| 166       this, l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_TRY_AGAIN)); |  | 
| 167   AddChildView(try_again_); |  | 
| 168 |  | 
| 169   mic_settings_ = new views::Link( |  | 
| 170       l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_MIC_SETTINGS)); |  | 
| 171   mic_settings_->set_listener(this); |  | 
| 172   AddChildView(mic_settings_); |  | 
| 173 } |  | 
| 174 |  | 
| 175 void SpeechInputBubbleView::UpdateLayout( |  | 
| 176     SpeechInputBubbleBase::DisplayMode mode, |  | 
| 177     const string16& message_text, |  | 
| 178     const SkBitmap& image) { |  | 
| 179   display_mode_ = mode; |  | 
| 180   bool is_message = (mode == SpeechInputBubbleBase::DISPLAY_MODE_MESSAGE); |  | 
| 181   icon_->SetVisible(!is_message); |  | 
| 182   message_->SetVisible(is_message); |  | 
| 183   mic_settings_->SetVisible(is_message); |  | 
| 184   try_again_->SetVisible(is_message); |  | 
| 185   cancel_->SetVisible(mode != SpeechInputBubbleBase::DISPLAY_MODE_WARM_UP); |  | 
| 186   heading_->SetVisible(mode == SpeechInputBubbleBase::DISPLAY_MODE_RECORDING); |  | 
| 187 |  | 
| 188   // Clickable elements should be enabled if and only if they are visible. |  | 
| 189   mic_settings_->SetEnabled(mic_settings_->visible()); |  | 
| 190   try_again_->SetEnabled(try_again_->visible()); |  | 
| 191   cancel_->SetEnabled(cancel_->visible()); |  | 
| 192 |  | 
| 193   if (is_message) { |  | 
| 194     message_->SetText(message_text); |  | 
| 195   } else { |  | 
| 196     SetImage(image); |  | 
| 197   } |  | 
| 198 |  | 
| 199   if (icon_->visible()) |  | 
| 200     icon_->ResetImageSize(); |  | 
| 201 |  | 
| 202   // When moving from warming up to recording state, the size of the content |  | 
| 203   // stays the same. So we wouldn't get a resize/layout call from the view |  | 
| 204   // system and we do it ourselves. |  | 
| 205   if (GetPreferredSize() == size())  // |size()| here is the current size. |  | 
| 206     Layout(); |  | 
| 207 |  | 
| 208   SizeToContents(); |  | 
| 209 } |  | 
| 210 |  | 
| 211 void SpeechInputBubbleView::SetImage(const SkBitmap& image) { |  | 
| 212   icon_->SetImage(image); |  | 
| 213 } |  | 
| 214 |  | 
| 215 void SpeechInputBubbleView::ButtonPressed(views::Button* source, |  | 
| 216                                           const views::Event& event) { |  | 
| 217   if (source == cancel_) { |  | 
| 218     delegate_->InfoBubbleButtonClicked(SpeechInputBubble::BUTTON_CANCEL); |  | 
| 219   } else if (source == try_again_) { |  | 
| 220     delegate_->InfoBubbleButtonClicked(SpeechInputBubble::BUTTON_TRY_AGAIN); |  | 
| 221   } else { |  | 
| 222     NOTREACHED() << "Unknown button"; |  | 
| 223   } |  | 
| 224 } |  | 
| 225 |  | 
| 226 void SpeechInputBubbleView::LinkClicked(views::Link* source, int event_flags) { |  | 
| 227   DCHECK_EQ(source, mic_settings_); |  | 
| 228   content::SpeechInputManager::GetInstance()->ShowAudioInputSettings(); |  | 
| 229 } |  | 
| 230 |  | 
| 231 gfx::Size SpeechInputBubbleView::GetPreferredSize() { |  | 
| 232   int width = heading_->GetPreferredSize().width(); |  | 
| 233   int control_width = cancel_->GetPreferredSize().width(); |  | 
| 234   if (try_again_->visible()) { |  | 
| 235     control_width += try_again_->GetPreferredSize().width() + |  | 
| 236                      views::kRelatedButtonHSpacing; |  | 
| 237   } |  | 
| 238   width = std::max(width, control_width); |  | 
| 239   control_width = std::max(icon_->GetPreferredSize().width(), |  | 
| 240                            kIconLayoutMinWidth); |  | 
| 241   width = std::max(width, control_width); |  | 
| 242   if (mic_settings_->visible()) { |  | 
| 243     control_width = mic_settings_->GetPreferredSize().width(); |  | 
| 244     width = std::max(width, control_width); |  | 
| 245   } |  | 
| 246 |  | 
| 247   int height = cancel_->GetPreferredSize().height(); |  | 
| 248   if (message_->visible()) { |  | 
| 249     height += message_->GetHeightForWidth(width) + |  | 
| 250               views::kLabelToControlVerticalSpacing; |  | 
| 251   } |  | 
| 252   if (heading_->visible()) |  | 
| 253     height += heading_->GetPreferredSize().height(); |  | 
| 254   if (icon_->visible()) |  | 
| 255     height += icon_->GetImage().height(); |  | 
| 256   if (mic_settings_->visible()) |  | 
| 257     height += mic_settings_->GetPreferredSize().height(); |  | 
| 258   width += kBubbleHorizMargin * 2; |  | 
| 259   height += kBubbleVertMargin * 2; |  | 
| 260 |  | 
| 261   return gfx::Size(width, height); |  | 
| 262 } |  | 
| 263 |  | 
| 264 void SpeechInputBubbleView::Layout() { |  | 
| 265   int x = kBubbleHorizMargin; |  | 
| 266   int y = kBubbleVertMargin; |  | 
| 267   int available_width = width() - kBubbleHorizMargin * 2; |  | 
| 268   int available_height = height() - kBubbleVertMargin * 2; |  | 
| 269 |  | 
| 270   if (message_->visible()) { |  | 
| 271     DCHECK(try_again_->visible()); |  | 
| 272 |  | 
| 273     int control_height = try_again_->GetPreferredSize().height(); |  | 
| 274     int try_again_width = try_again_->GetPreferredSize().width(); |  | 
| 275     int cancel_width = cancel_->GetPreferredSize().width(); |  | 
| 276     y += available_height - control_height; |  | 
| 277     x += (available_width - cancel_width - try_again_width - |  | 
| 278           views::kRelatedButtonHSpacing) / 2; |  | 
| 279     try_again_->SetBounds(x, y, try_again_width, control_height); |  | 
| 280     cancel_->SetBounds(x + try_again_width + views::kRelatedButtonHSpacing, y, |  | 
| 281                        cancel_width, control_height); |  | 
| 282 |  | 
| 283     control_height = message_->GetHeightForWidth(available_width); |  | 
| 284     message_->SetBounds(kBubbleHorizMargin, kBubbleVertMargin, |  | 
| 285                         available_width, control_height); |  | 
| 286     y = kBubbleVertMargin + control_height; |  | 
| 287 |  | 
| 288     control_height = mic_settings_->GetPreferredSize().height(); |  | 
| 289     mic_settings_->SetBounds(kBubbleHorizMargin, y, available_width, |  | 
| 290                              control_height); |  | 
| 291   } else { |  | 
| 292     DCHECK(icon_->visible()); |  | 
| 293 |  | 
| 294     int control_height = icon_->GetImage().height(); |  | 
| 295     if (display_mode_ == SpeechInputBubbleBase::DISPLAY_MODE_WARM_UP) |  | 
| 296       y = (available_height - control_height) / 2; |  | 
| 297     icon_->SetBounds(x, y, available_width, control_height); |  | 
| 298     y += control_height; |  | 
| 299 |  | 
| 300     if (heading_->visible()) { |  | 
| 301       control_height = heading_->GetPreferredSize().height(); |  | 
| 302       heading_->SetBounds(x, y, available_width, control_height); |  | 
| 303       y += control_height; |  | 
| 304     } |  | 
| 305 |  | 
| 306     if (cancel_->visible()) { |  | 
| 307       control_height = cancel_->GetPreferredSize().height(); |  | 
| 308       int width = cancel_->GetPreferredSize().width(); |  | 
| 309       cancel_->SetBounds(x + (available_width - width) / 2, y, width, |  | 
| 310                          control_height); |  | 
| 311     } |  | 
| 312   } |  | 
| 313 } |  | 
| 314 |  | 
| 315 // Implementation of SpeechInputBubble. |  | 
| 316 class SpeechInputBubbleImpl : public SpeechInputBubbleBase { |  | 
| 317  public: |  | 
| 318   SpeechInputBubbleImpl(WebContents* web_contents, |  | 
| 319                         Delegate* delegate, |  | 
| 320                         const gfx::Rect& element_rect); |  | 
| 321   virtual ~SpeechInputBubbleImpl(); |  | 
| 322 |  | 
| 323   // SpeechInputBubble methods. |  | 
| 324   virtual void Show() OVERRIDE; |  | 
| 325   virtual void Hide() OVERRIDE; |  | 
| 326 |  | 
| 327   // SpeechInputBubbleBase methods. |  | 
| 328   virtual void UpdateLayout() OVERRIDE; |  | 
| 329   virtual void UpdateImage() OVERRIDE; |  | 
| 330 |  | 
| 331  private: |  | 
| 332   Delegate* delegate_; |  | 
| 333   SpeechInputBubbleView* bubble_; |  | 
| 334   gfx::Rect element_rect_; |  | 
| 335 |  | 
| 336   DISALLOW_COPY_AND_ASSIGN(SpeechInputBubbleImpl); |  | 
| 337 }; |  | 
| 338 |  | 
| 339 SpeechInputBubbleImpl::SpeechInputBubbleImpl(WebContents* web_contents, |  | 
| 340                                              Delegate* delegate, |  | 
| 341                                              const gfx::Rect& element_rect) |  | 
| 342     : SpeechInputBubbleBase(web_contents), |  | 
| 343       delegate_(delegate), |  | 
| 344       bubble_(NULL), |  | 
| 345       element_rect_(element_rect) { |  | 
| 346 } |  | 
| 347 |  | 
| 348 SpeechInputBubbleImpl::~SpeechInputBubbleImpl() { |  | 
| 349   if (bubble_) { |  | 
| 350     bubble_->set_notify_delegate_on_activation_change(false); |  | 
| 351     bubble_->GetWidget()->Close(); |  | 
| 352   } |  | 
| 353 } |  | 
| 354 |  | 
| 355 void SpeechInputBubbleImpl::Show() { |  | 
| 356   if (!bubble_) { |  | 
| 357     // Anchor to the location icon view, in case |element_rect| is offscreen. |  | 
| 358     Browser* browser = Browser::GetOrCreateTabbedBrowser( |  | 
| 359         Profile::FromBrowserContext(web_contents()->GetBrowserContext())); |  | 
| 360     BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); |  | 
| 361     views::View* icon = browser_view->GetLocationBarView() ? |  | 
| 362         browser_view->GetLocationBarView()->location_icon_view() : NULL; |  | 
| 363     bubble_ = new SpeechInputBubbleView(delegate_, icon, element_rect_, |  | 
| 364                                         web_contents()); |  | 
| 365     browser::CreateViewsBubble(bubble_); |  | 
| 366     UpdateLayout(); |  | 
| 367   } |  | 
| 368   bubble_->Show(); |  | 
| 369 } |  | 
| 370 |  | 
| 371 void SpeechInputBubbleImpl::Hide() { |  | 
| 372   if (bubble_) |  | 
| 373     bubble_->GetWidget()->Hide(); |  | 
| 374 } |  | 
| 375 |  | 
| 376 void SpeechInputBubbleImpl::UpdateLayout() { |  | 
| 377   if (bubble_) |  | 
| 378     bubble_->UpdateLayout(display_mode(), message_text(), icon_image()); |  | 
| 379 } |  | 
| 380 |  | 
| 381 void SpeechInputBubbleImpl::UpdateImage() { |  | 
| 382   if (bubble_) |  | 
| 383     bubble_->SetImage(icon_image()); |  | 
| 384 } |  | 
| 385 |  | 
| 386 }  // namespace |  | 
| 387 |  | 
| 388 SpeechInputBubble* SpeechInputBubble::CreateNativeBubble( |  | 
| 389     WebContents* web_contents, |  | 
| 390     SpeechInputBubble::Delegate* delegate, |  | 
| 391     const gfx::Rect& element_rect) { |  | 
| 392   return new SpeechInputBubbleImpl(web_contents, delegate, element_rect); |  | 
| 393 } |  | 
| OLD | NEW | 
|---|