Home | History | Annotate | Download | only in speech
      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 "base/utf_string_conversions.h"
      8 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
      9 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     10 #include "chrome/browser/ui/gtk/gtk_util.h"
     11 #include "chrome/browser/ui/gtk/info_bubble_gtk.h"
     12 #include "chrome/browser/ui/gtk/owned_widget_gtk.h"
     13 #include "content/browser/tab_contents/tab_contents.h"
     14 #include "grit/generated_resources.h"
     15 #include "grit/theme_resources.h"
     16 #include "media/audio/audio_manager.h"
     17 #include "ui/base/l10n/l10n_util.h"
     18 #include "ui/base/resource/resource_bundle.h"
     19 #include "ui/gfx/gtk_util.h"
     20 #include "ui/gfx/rect.h"
     21 
     22 namespace {
     23 
     24 const int kBubbleControlVerticalSpacing = 5;
     25 const int kBubbleControlHorizontalSpacing = 20;
     26 const int kIconHorizontalPadding = 10;
     27 const int kButtonBarHorizontalSpacing = 10;
     28 
     29 // Use black for text labels since the bubble has white background.
     30 const GdkColor kLabelTextColor = gtk_util::kGdkBlack;
     31 
     32 // Implementation of SpeechInputBubble for GTK. This shows a speech input
     33 // info bubble on screen.
     34 class SpeechInputBubbleGtk
     35     : public SpeechInputBubbleBase,
     36       public InfoBubbleGtkDelegate {
     37  public:
     38   SpeechInputBubbleGtk(TabContents* tab_contents,
     39                        Delegate* delegate,
     40                        const gfx::Rect& element_rect);
     41   ~SpeechInputBubbleGtk();
     42 
     43  private:
     44   // InfoBubbleDelegate methods.
     45   virtual void InfoBubbleClosing(InfoBubbleGtk* info_bubble,
     46                                  bool closed_by_escape);
     47 
     48   // SpeechInputBubble methods.
     49   virtual void Show();
     50   virtual void Hide();
     51   virtual void UpdateLayout();
     52   virtual void UpdateImage();
     53 
     54   CHROMEGTK_CALLBACK_0(SpeechInputBubbleGtk, void, OnCancelClicked);
     55   CHROMEGTK_CALLBACK_0(SpeechInputBubbleGtk, void, OnTryAgainClicked);
     56   CHROMEGTK_CALLBACK_0(SpeechInputBubbleGtk, void, OnMicSettingsClicked);
     57 
     58   Delegate* delegate_;
     59   InfoBubbleGtk* info_bubble_;
     60   gfx::Rect element_rect_;
     61   bool did_invoke_close_;
     62 
     63   GtkWidget* label_;
     64   GtkWidget* cancel_button_;
     65   GtkWidget* try_again_button_;
     66   GtkWidget* icon_;
     67   GtkWidget* icon_container_;
     68   GtkWidget* mic_settings_;
     69 
     70   DISALLOW_COPY_AND_ASSIGN(SpeechInputBubbleGtk);
     71 };
     72 
     73 SpeechInputBubbleGtk::SpeechInputBubbleGtk(TabContents* tab_contents,
     74                                            Delegate* delegate,
     75                                            const gfx::Rect& element_rect)
     76     : SpeechInputBubbleBase(tab_contents),
     77       delegate_(delegate),
     78       info_bubble_(NULL),
     79       element_rect_(element_rect),
     80       did_invoke_close_(false),
     81       label_(NULL),
     82       cancel_button_(NULL),
     83       try_again_button_(NULL),
     84       icon_(NULL),
     85       icon_container_(NULL),
     86       mic_settings_(NULL) {
     87 }
     88 
     89 SpeechInputBubbleGtk::~SpeechInputBubbleGtk() {
     90   // The |Close| call below invokes our |InfoBubbleClosing| method. Since we
     91   // were destroyed by the caller we don't need to call them back, hence set
     92   // this flag here.
     93   did_invoke_close_ = true;
     94   Hide();
     95 }
     96 
     97 void SpeechInputBubbleGtk::InfoBubbleClosing(InfoBubbleGtk* info_bubble,
     98                                              bool closed_by_escape) {
     99   info_bubble_ = NULL;
    100   if (!did_invoke_close_)
    101     delegate_->InfoBubbleFocusChanged();
    102 }
    103 
    104 void SpeechInputBubbleGtk::OnCancelClicked(GtkWidget* widget) {
    105   delegate_->InfoBubbleButtonClicked(BUTTON_CANCEL);
    106 }
    107 
    108 void SpeechInputBubbleGtk::OnTryAgainClicked(GtkWidget* widget) {
    109   delegate_->InfoBubbleButtonClicked(BUTTON_TRY_AGAIN);
    110 }
    111 
    112 void SpeechInputBubbleGtk::OnMicSettingsClicked(GtkWidget* widget) {
    113   AudioManager::GetAudioManager()->ShowAudioInputSettings();
    114 }
    115 
    116 void SpeechInputBubbleGtk::Show() {
    117   if (info_bubble_)
    118     return;  // Nothing further to do since the bubble is already visible.
    119 
    120   // We use a vbox to arrange the controls (label, image, button bar) vertically
    121   // and the button bar is a hbox holding the 2 buttons (try again and cancel).
    122   // To get horizontal space around them we place this vbox with padding in a
    123   // GtkAlignment below.
    124   GtkWidget* vbox = gtk_vbox_new(FALSE, 0);
    125 
    126   // The icon with a some padding on the left and right.
    127   icon_container_ = gtk_alignment_new(0, 0, 0, 0);
    128   icon_ = gtk_image_new();
    129   gtk_container_add(GTK_CONTAINER(icon_container_), icon_);
    130   gtk_box_pack_start(GTK_BOX(vbox), icon_container_, FALSE, FALSE,
    131                      kBubbleControlVerticalSpacing);
    132 
    133   label_ = gtk_label_new(NULL);
    134   gtk_util::SetLabelColor(label_, &kLabelTextColor);
    135   gtk_box_pack_start(GTK_BOX(vbox), label_, FALSE, FALSE,
    136                      kBubbleControlVerticalSpacing);
    137 
    138   if (AudioManager::GetAudioManager()->CanShowAudioInputSettings()) {
    139     mic_settings_ = gtk_chrome_link_button_new(
    140         l10n_util::GetStringUTF8(IDS_SPEECH_INPUT_MIC_SETTINGS).c_str());
    141     gtk_box_pack_start(GTK_BOX(vbox), mic_settings_, FALSE, FALSE,
    142                        kBubbleControlVerticalSpacing);
    143     g_signal_connect(mic_settings_, "clicked",
    144                      G_CALLBACK(&OnMicSettingsClickedThunk), this);
    145   }
    146 
    147   GtkWidget* button_bar = gtk_hbox_new(FALSE, kButtonBarHorizontalSpacing);
    148   gtk_box_pack_start(GTK_BOX(vbox), button_bar, FALSE, FALSE,
    149                      kBubbleControlVerticalSpacing);
    150 
    151   cancel_button_ = gtk_button_new_with_label(
    152       l10n_util::GetStringUTF8(IDS_CANCEL).c_str());
    153   gtk_box_pack_start(GTK_BOX(button_bar), cancel_button_, TRUE, FALSE, 0);
    154   g_signal_connect(cancel_button_, "clicked",
    155                    G_CALLBACK(&OnCancelClickedThunk), this);
    156 
    157   try_again_button_ = gtk_button_new_with_label(
    158       l10n_util::GetStringUTF8(IDS_SPEECH_INPUT_TRY_AGAIN).c_str());
    159   gtk_box_pack_start(GTK_BOX(button_bar), try_again_button_, TRUE, FALSE, 0);
    160   g_signal_connect(try_again_button_, "clicked",
    161                    G_CALLBACK(&OnTryAgainClickedThunk), this);
    162 
    163   GtkWidget* content = gtk_alignment_new(0, 0, 0, 0);
    164   gtk_alignment_set_padding(GTK_ALIGNMENT(content),
    165       kBubbleControlVerticalSpacing, kBubbleControlVerticalSpacing,
    166       kBubbleControlHorizontalSpacing, kBubbleControlHorizontalSpacing);
    167   gtk_container_add(GTK_CONTAINER(content), vbox);
    168 
    169   GtkThemeService* theme_provider = GtkThemeService::GetFrom(
    170       tab_contents()->profile());
    171   gfx::Rect rect(
    172       element_rect_.x() + element_rect_.width() - kBubbleTargetOffsetX,
    173       element_rect_.y() + element_rect_.height(), 1, 1);
    174   info_bubble_ = InfoBubbleGtk::Show(tab_contents()->GetNativeView(),
    175                                      &rect,
    176                                      content,
    177                                      InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT,
    178                                      false,  // match_system_theme
    179                                      true,  // grab_input
    180                                      theme_provider,
    181                                      this);
    182 
    183   UpdateLayout();
    184 }
    185 
    186 void SpeechInputBubbleGtk::Hide() {
    187   if (info_bubble_)
    188     info_bubble_->Close();
    189 }
    190 
    191 void SpeechInputBubbleGtk::UpdateLayout() {
    192   if (!info_bubble_)
    193     return;
    194 
    195   if (display_mode() == DISPLAY_MODE_MESSAGE) {
    196     // Message text and the Try Again + Cancel buttons are visible, hide the
    197     // icon.
    198     gtk_label_set_text(GTK_LABEL(label_),
    199                        UTF16ToUTF8(message_text()).c_str());
    200     gtk_widget_show(label_);
    201     gtk_widget_show(try_again_button_);
    202     if (mic_settings_)
    203       gtk_widget_show(mic_settings_);
    204     gtk_widget_hide(icon_);
    205   } else {
    206     // Heading text, icon and cancel button are visible, hide the Try Again
    207     // button.
    208     gtk_label_set_text(GTK_LABEL(label_),
    209         l10n_util::GetStringUTF8(IDS_SPEECH_INPUT_BUBBLE_HEADING).c_str());
    210     if (display_mode() == DISPLAY_MODE_RECORDING) {
    211       gtk_widget_show(label_);
    212     } else {
    213       gtk_widget_hide(label_);
    214     }
    215     UpdateImage();
    216     gtk_widget_show(icon_);
    217     gtk_widget_hide(try_again_button_);
    218     if (mic_settings_)
    219       gtk_widget_hide(mic_settings_);
    220     if (display_mode() == DISPLAY_MODE_WARM_UP) {
    221       gtk_widget_hide(cancel_button_);
    222 
    223       // The text label and cancel button are hidden in this mode, but we want
    224       // the popup to appear the same size as it would once recording starts,
    225       // so as to reduce UI jank when recording starts. So we calculate the
    226       // difference in size between the two sets of controls and add that as
    227       // padding around the icon here.
    228       GtkRequisition cancel_size;
    229       gtk_widget_get_child_requisition(cancel_button_, &cancel_size);
    230       GtkRequisition label_size;
    231       gtk_widget_get_child_requisition(label_, &label_size);
    232       SkBitmap* volume = ResourceBundle::GetSharedInstance().GetBitmapNamed(
    233           IDR_SPEECH_INPUT_MIC_EMPTY);
    234       int desired_width = std::max(volume->width(), cancel_size.width) +
    235                           kIconHorizontalPadding * 2;
    236       int desired_height = volume->height() + label_size.height +
    237                            cancel_size.height +
    238                            kBubbleControlVerticalSpacing * 2;
    239       int diff_width = desired_width - icon_image().width();
    240       int diff_height = desired_height - icon_image().height();
    241       gtk_alignment_set_padding(GTK_ALIGNMENT(icon_container_),
    242                                 diff_height / 2, diff_height - diff_height / 2,
    243                                 diff_width / 2, diff_width - diff_width / 2);
    244     } else {
    245       // Reset the padding done above.
    246       gtk_alignment_set_padding(GTK_ALIGNMENT(icon_container_), 0, 0,
    247                                 kIconHorizontalPadding, kIconHorizontalPadding);
    248       gtk_widget_show(cancel_button_);
    249     }
    250   }
    251 }
    252 
    253 void SpeechInputBubbleGtk::UpdateImage() {
    254   SkBitmap image = icon_image();
    255   if (image.isNull() || !info_bubble_)
    256     return;
    257 
    258   GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&image);
    259   gtk_image_set_from_pixbuf(GTK_IMAGE(icon_), pixbuf);
    260   g_object_unref(pixbuf);
    261 }
    262 
    263 }  // namespace
    264 
    265 SpeechInputBubble* SpeechInputBubble::CreateNativeBubble(
    266     TabContents* tab_contents,
    267     Delegate* delegate,
    268     const gfx::Rect& element_rect) {
    269   return new SpeechInputBubbleGtk(tab_contents, delegate, element_rect);
    270 }
    271 
    272