diff options
author | satish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-24 17:06:49 +0000 |
---|---|---|
committer | satish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-24 17:06:49 +0000 |
commit | 00ef0bfd38eadb7e6f0e326fab2231895b5e0edb (patch) | |
tree | 4d579dae9ef7cea72b33fc68606f04f4d753e9d0 | |
parent | 1fd39e60501ca72f0bba0fd97a1390b0cde6870f (diff) | |
download | chromium_src-00ef0bfd38eadb7e6f0e326fab2231895b5e0edb.zip chromium_src-00ef0bfd38eadb7e6f0e326fab2231895b5e0edb.tar.gz chromium_src-00ef0bfd38eadb7e6f0e326fab2231895b5e0edb.tar.bz2 |
Adds a speech input info bubble.
I have not implemented the Mac/Linux part yet, will do so in a subsequent CL (does InfoBubble exist for Mac?).
This info bubble remains visible on screen until recognition ends or the user cancels it.
It also tracks the html element which requested speech recognition if it changes position
on screen (e.g. user scrolls the page or moves the browser window).
To get this working, I extended InfoBubble to query the delegate if it can be closed when
another window gets activated, as well as to reposition it when required.
TEST=none
BUG=none
Review URL: http://codereview.chromium.org/3133034
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@57199 0039d316-1c4b-4281-b951-d872f2087c98
-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', |