diff options
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/app/generated_resources.grd | 4 | ||||
-rw-r--r-- | chrome/app/theme/speech_input_processing.png | bin | 0 -> 1422 bytes | |||
-rw-r--r-- | chrome/app/theme/speech_input_recording.png | bin | 0 -> 2592 bytes | |||
-rw-r--r-- | chrome/app/theme/theme_resources.grd | 2 | ||||
-rw-r--r-- | chrome/browser/speech/speech_input_bubble.h | 58 | ||||
-rw-r--r-- | chrome/browser/views/speech_input_bubble.cc | 258 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 |
7 files changed, 324 insertions, 0 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index b33fb6d..d4f5e05f 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -9336,6 +9336,10 @@ Keep your key file in a safe place. You will need it to create new versions of y It looks like you've moved. Would you like to use <ph name="NEW_GOOGLE_URL">$1<ex>google.com</ex></ph>? </message> + <message name="IDS_SPEECH_INPUT_BUBBLE_HEADING" desc="First line in the content area of the speech input bubble. Instructs the user that they can start speaking."> + Speak now + </message> + </messages> <structures fallback_to_english="true"> diff --git a/chrome/app/theme/speech_input_processing.png b/chrome/app/theme/speech_input_processing.png Binary files differnew file mode 100644 index 0000000..0529e35 --- /dev/null +++ b/chrome/app/theme/speech_input_processing.png diff --git a/chrome/app/theme/speech_input_recording.png b/chrome/app/theme/speech_input_recording.png Binary files differnew file mode 100644 index 0000000..3644c2e --- /dev/null +++ b/chrome/app/theme/speech_input_recording.png diff --git a/chrome/app/theme/theme_resources.grd b/chrome/app/theme/theme_resources.grd index fa2bab1..997200c 100644 --- a/chrome/app/theme/theme_resources.grd +++ b/chrome/app/theme/theme_resources.grd @@ -414,6 +414,8 @@ <include name="IDR_UPGRADE_DOT_INACTIVE" file="upgrade_dot_inactive.png" type="BINDATA" /> <include name="IDR_INFO" file="info_small.png" type="BINDATA" /> <include name="IDR_WARNING" file="alert_small.png" type="BINDATA" /> + <include name="IDR_SPEECH_INPUT_RECORDING" file="speech_input_recording.png" type="BINDATA" /> + <include name="IDR_SPEECH_INPUT_PROCESSING" file="speech_input_processing.png" type="BINDATA" /> <if expr="pp_ifdef('_google_chrome')"> <include name="IDR_WIZARD_ICON" file="google_chrome/wizard_icon.png" type="BINDATA" /> <include name="IDR_WIZARD_ICON_RTL" file="google_chrome/wizard_icon_rtl.png" type="BINDATA" /> diff --git a/chrome/browser/speech/speech_input_bubble.h b/chrome/browser/speech/speech_input_bubble.h new file mode 100644 index 0000000..4ddab1b --- /dev/null +++ b/chrome/browser/speech/speech_input_bubble.h @@ -0,0 +1,58 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SPEECH_SPEECH_INPUT_BUBBLE_H_ +#define CHROME_BROWSER_SPEECH_SPEECH_INPUT_BUBBLE_H_ +#pragma once + +#include "gfx/rect.h" + +class TabContents; + +// SpeechInputBubble displays a popup info bubble during speech recognition, +// points to the html element which requested speech input and shows recognition +// progress events. The popup is closed by the user clicking anywhere outside +// the popup window, or by the caller destroying this object. +class SpeechInputBubble { + public: + // Informs listeners of user actions in the bubble. + class Delegate { + public: + // Invoked when the user cancels speech recognition by clicking on the + // cancel button. The InfoBubble is still active and the caller should close + // it if necessary. + virtual void RecognitionCancelled() = 0; + + // Invoked when the user clicks outside the InfoBubble causing it to close. + // The InfoBubble window is no longer visible on screen and the caller can + // free the InfoBubble instance. This callback is not issued if the bubble + // got closed because the object was destroyed by the caller. + virtual void InfoBubbleClosed() = 0; + + protected: + virtual ~Delegate() { + } + }; + + // Factory method to create new instances. + // Creates and displays the bubble. + // |tab_contents| is the TabContents hosting the page. + // |element_rect| is the display bounds of the html element requesting speech + // input (in page coordinates). + static SpeechInputBubble* Create(TabContents* tab_contents, + Delegate* delegate, + const gfx::Rect& element_rect); + virtual ~SpeechInputBubble() {} + + // Indicates to the user that recognition is in progress. + virtual void SetRecognizingMode() = 0; +}; + +// This typedef is to workaround the issue with certain versions of +// Visual Studio where it gets confused between multiple Delegate +// classes and gives a C2500 error. (I saw this error on the try bots - +// the workaround was not needed for my machine). +typedef SpeechInputBubble::Delegate SpeechInputBubbleDelegate; + +#endif // CHROME_BROWSER_SPEECH_SPEECH_INPUT_BUBBLE_H_ diff --git a/chrome/browser/views/speech_input_bubble.cc b/chrome/browser/views/speech_input_bubble.cc new file mode 100644 index 0000000..1722227 --- /dev/null +++ b/chrome/browser/views/speech_input_bubble.cc @@ -0,0 +1,258 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/speech/speech_input_bubble.h" + +#include "app/l10n_util.h" +#include "app/resource_bundle.h" +#include "base/message_loop.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_view.h" +#include "chrome/browser/views/info_bubble.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_type.h" +#include "gfx/canvas.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "views/controls/image_view.h" +#include "views/controls/label.h" +#include "views/controls/link.h" +#include "views/standard_layout.h" +#include "views/view.h" + +namespace { + +// The speech bubble's arrow is so many pixels from the left of html element. +const int kBubbleTargetOffsetX = 5; +const int kBubbleHorizMargin = 40; +const int kBubbleVertMargin = 0; + +// This is the content view which is placed inside a SpeechInputBubble. +class ContentView + : public views::View, + public views::LinkController { + public: + explicit ContentView(SpeechInputBubbleDelegate* delegate); + + void SetRecognizingMode(); + + // views::LinkController methods. + virtual void LinkActivated(views::Link* source, int event_flags); + + // views::View overrides. + virtual gfx::Size GetPreferredSize(); + virtual void Layout(); + + private: + SpeechInputBubbleDelegate* delegate_; + views::ImageView* icon_; + views::Label* heading_; + views::Link* cancel_; + + DISALLOW_COPY_AND_ASSIGN(ContentView); +}; + +ContentView::ContentView(SpeechInputBubbleDelegate* delegate) + : delegate_(delegate) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + const gfx::Font& font = rb.GetFont(ResourceBundle::BaseFont); + + heading_ = new views::Label( + l10n_util::GetString(IDS_SPEECH_INPUT_BUBBLE_HEADING)); + heading_->SetFont(font.DeriveFont(3, gfx::Font::NORMAL)); + heading_->SetHorizontalAlignment(views::Label::ALIGN_CENTER); + AddChildView(heading_); + + icon_ = new views::ImageView(); + icon_->SetImage(*ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_SPEECH_INPUT_RECORDING)); + icon_->SetHorizontalAlignment(views::ImageView::CENTER); + AddChildView(icon_); + + cancel_ = new views::Link(l10n_util::GetString(IDS_CANCEL)); + cancel_->SetController(this); + cancel_->SetFont(font.DeriveFont(3, gfx::Font::NORMAL)); + cancel_->SetHorizontalAlignment(views::Label::ALIGN_CENTER); + AddChildView(cancel_); +} + +void ContentView::SetRecognizingMode() { + icon_->SetImage(*ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_SPEECH_INPUT_PROCESSING)); +} + +void ContentView::LinkActivated(views::Link* source, int event_flags) { + if (source == cancel_) { + delegate_->RecognitionCancelled(); + } else { + NOTREACHED() << "Unknown view"; + } +} + +gfx::Size ContentView::GetPreferredSize() { + int width = heading_->GetPreferredSize().width(); + int control_width = cancel_->GetPreferredSize().width(); + if (control_width > width) + width = control_width; + control_width = icon_->GetPreferredSize().width(); + if (control_width > width) + width = control_width; + width += kBubbleHorizMargin * 2; + + int height = kBubbleVertMargin * 2 + + heading_->GetPreferredSize().height() + + cancel_->GetPreferredSize().height() + + icon_->GetImage().height(); + return gfx::Size(width, height); +} + +void ContentView::Layout() { + int x = kBubbleHorizMargin; + int y = kBubbleVertMargin; + int control_width = width() - kBubbleHorizMargin * 2; + + int height = heading_->GetPreferredSize().height(); + heading_->SetBounds(x, y, control_width, height); + y += height; + + height = icon_->GetImage().height(); + icon_->SetBounds(x, y, control_width, height); + y += height; + + height = cancel_->GetPreferredSize().height(); + cancel_->SetBounds(x, y, control_width, height); +} + +// Implementation of SpeechInputBubble. +class SpeechInputBubbleImpl + : public SpeechInputBubble, + public InfoBubbleDelegate, + public NotificationObserver { + public: + SpeechInputBubbleImpl(TabContents* tab_contents, + Delegate* delegate, + const gfx::Rect& element_rect); + virtual ~SpeechInputBubbleImpl(); + + virtual void SetRecognizingMode(); + + // Returns the screen rectangle to use as the info bubble's target. + // |element_rect| is the html element's bounds in page coordinates. + gfx::Rect GetInfoBubbleTarget(const gfx::Rect& element_rect); + + // NotificationObserver implementation. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // InfoBubbleDelegate + virtual void InfoBubbleClosing(InfoBubble* info_bubble, + bool closed_by_escape); + virtual bool CloseOnEscape(); + virtual bool FadeInOnShow(); + + private: + Delegate* delegate_; + InfoBubble* info_bubble_; + TabContents* tab_contents_; + ContentView* bubble_content_; + NotificationRegistrar registrar_; + + // Set to true if the object is being destroyed normally instead of the + // user clicking outside the window causing it to close automatically. + bool did_invoke_close_; + + DISALLOW_COPY_AND_ASSIGN(SpeechInputBubbleImpl); +}; + +SpeechInputBubbleImpl::SpeechInputBubbleImpl(TabContents* tab_contents, + Delegate* delegate, + const gfx::Rect& element_rect) + : delegate_(delegate), + info_bubble_(NULL), + tab_contents_(tab_contents), + bubble_content_(NULL), + did_invoke_close_(false) { + bubble_content_ = new ContentView(delegate_); + + views::Widget* parent = views::Widget::GetWidgetFromNativeWindow( + tab_contents_->view()->GetTopLevelNativeWindow()); + info_bubble_ = InfoBubble::Show(parent, + GetInfoBubbleTarget(element_rect), + BubbleBorder::TOP_LEFT, bubble_content_, + this); + + // We don't want fade outs when closing because it makes speech recognition + // appear slower than it is. Also setting it to false allows |Close| to + // destroy the bubble immediately instead of waiting for the fade animation + // to end so the caller can manage this object's life cycle like a normal + // stack based or member variable object. + info_bubble_->set_fade_away_on_close(false); + + registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED, + Source<TabContents>(tab_contents_)); +} + +SpeechInputBubbleImpl::~SpeechInputBubbleImpl() { + if (info_bubble_) { + did_invoke_close_ = true; + info_bubble_->Close(); + } +} + +void SpeechInputBubbleImpl::SetRecognizingMode() { + DCHECK(info_bubble_); + DCHECK(bubble_content_); + bubble_content_->SetRecognizingMode(); +} + +gfx::Rect SpeechInputBubbleImpl::GetInfoBubbleTarget( + const gfx::Rect& element_rect) { + gfx::Rect container_rect; + tab_contents_->GetContainerBounds(&container_rect); + return gfx::Rect( + container_rect.x() + element_rect.x() + kBubbleTargetOffsetX, + container_rect.y() + element_rect.y() + element_rect.height(), 1, 1); +} + +void SpeechInputBubbleImpl::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::TAB_CONTENTS_DESTROYED) { + delegate_->RecognitionCancelled(); + } else { + NOTREACHED() << "Unknown notification"; + } +} + +void SpeechInputBubbleImpl::InfoBubbleClosing(InfoBubble* info_bubble, + bool closed_by_escape) { + registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED, + Source<TabContents>(tab_contents_)); + info_bubble_ = NULL; + bubble_content_ = NULL; + if (!did_invoke_close_) + delegate_->InfoBubbleClosed(); +} + +bool SpeechInputBubbleImpl::CloseOnEscape() { + return false; +} + +bool SpeechInputBubbleImpl::FadeInOnShow() { + return false; +} + +} // namespace + +SpeechInputBubble* SpeechInputBubble::Create( + TabContents* tab_contents, + SpeechInputBubble::Delegate* delegate, + const gfx::Rect& element_rect) { + return new SpeechInputBubbleImpl(tab_contents, delegate, element_rect); +} diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index a65339a..f0d254b 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2422,6 +2422,7 @@ 'browser/sidebar/sidebar_container.h', 'browser/sidebar/sidebar_manager.cc', 'browser/sidebar/sidebar_manager.h', + 'browser/speech/speech_input_bubble.h', 'browser/speech/speech_input_dispatcher_host.cc', 'browser/speech/speech_input_dispatcher_host.h', 'browser/speech/speech_input_manager.cc', @@ -2928,6 +2929,7 @@ 'browser/views/sad_tab_view.h', 'browser/views/select_file_dialog.cc', 'browser/views/shell_dialogs_win.cc', + 'browser/views/speech_input_bubble.cc', 'browser/views/ssl_client_certificate_selector_win.cc', 'browser/views/status_bubble_views.cc', 'browser/views/status_bubble_views.h', |