speech_input_bubble_gtk.cc revision ac1e49eb6695f711d72215fcdf9388548942a00d
12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright (c) 2010 The Chromium Authors. All rights reserved. 22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file. 42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/speech/speech_input_bubble.h" 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "app/l10n_util.h" 82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "app/resource_bundle.h" 92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/utf_string_conversions.h" 102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/gtk/gtk_theme_provider.h" 112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/gtk/gtk_util.h" 122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/gtk/info_bubble_gtk.h" 132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/gtk/owned_widget_gtk.h" 142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/tab_contents/tab_contents.h" 152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "gfx/gtk_util.h" 162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "gfx/rect.h" 172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "grit/generated_resources.h" 182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "grit/theme_resources.h" 192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace { 212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)const int kBubbleControlVerticalSpacing = 10; 232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)const int kBubbleControlHorizontalSpacing = 20; 242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)const int kIconHorizontalPadding = 30; 252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)const int kButtonBarHorizontalSpacing = 10; 262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use black for text labels since the bubble has white background. 282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)const GdkColor kLabelTextColor = gtk_util::kGdkBlack; 292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Implementation of SpeechInputBubble for GTK. This shows a speech input 312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// info bubble on screen. 322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class SpeechInputBubbleGtk 332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) : public SpeechInputBubbleBase, 342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) public InfoBubbleGtkDelegate { 352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) public: 362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) SpeechInputBubbleGtk(TabContents* tab_contents, 372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Delegate* delegate, 382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const gfx::Rect& element_rect); 392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ~SpeechInputBubbleGtk(); 402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) private: 422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // InfoBubbleDelegate methods. 432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) virtual void InfoBubbleClosing(InfoBubbleGtk* info_bubble, 442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) bool closed_by_escape); 452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // SpeechInputBubble methods. 472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) virtual void Show(); 482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) virtual void Hide(); 492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) virtual void UpdateLayout(); 502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) virtual void SetImage(const SkBitmap& image); 512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) CHROMEGTK_CALLBACK_0(SpeechInputBubbleGtk, void, OnCancelClicked); 537dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch CHROMEGTK_CALLBACK_0(SpeechInputBubbleGtk, void, OnTryAgainClicked); 542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Delegate* delegate_; 562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) InfoBubbleGtk* info_bubble_; 57 gfx::Rect element_rect_; 58 bool did_invoke_close_; 59 60 GtkWidget* label_; 61 GtkWidget* try_again_button_; 62 GtkWidget* icon_; 63 64 DISALLOW_COPY_AND_ASSIGN(SpeechInputBubbleGtk); 65}; 66 67SpeechInputBubbleGtk::SpeechInputBubbleGtk(TabContents* tab_contents, 68 Delegate* delegate, 69 const gfx::Rect& element_rect) 70 : SpeechInputBubbleBase(tab_contents), 71 delegate_(delegate), 72 info_bubble_(NULL), 73 element_rect_(element_rect), 74 did_invoke_close_(false), 75 label_(NULL), 76 try_again_button_(NULL), 77 icon_(NULL) { 78} 79 80SpeechInputBubbleGtk::~SpeechInputBubbleGtk() { 81 // The |Close| call below invokes our |InfoBubbleClosing| method. Since we 82 // were destroyed by the caller we don't need to call them back, hence set 83 // this flag here. 84 did_invoke_close_ = true; 85 Hide(); 86} 87 88void SpeechInputBubbleGtk::InfoBubbleClosing(InfoBubbleGtk* info_bubble, 89 bool closed_by_escape) { 90 info_bubble_ = NULL; 91 if (!did_invoke_close_) 92 delegate_->InfoBubbleFocusChanged(); 93} 94 95void SpeechInputBubbleGtk::OnCancelClicked(GtkWidget* widget) { 96 delegate_->InfoBubbleButtonClicked(BUTTON_CANCEL); 97} 98 99void SpeechInputBubbleGtk::OnTryAgainClicked(GtkWidget* widget) { 100 delegate_->InfoBubbleButtonClicked(BUTTON_TRY_AGAIN); 101} 102 103void SpeechInputBubbleGtk::Show() { 104 if (info_bubble_) 105 return; // Nothing further to do since the bubble is already visible. 106 107 // We use a vbox to arrange the controls (label, image, button bar) vertically 108 // and the button bar is a hbox holding the 2 buttons (try again and cancel). 109 // To get horizontal space around them we place this vbox with padding in a 110 // GtkAlignment below. 111 GtkWidget* vbox = gtk_vbox_new(FALSE, kBubbleControlVerticalSpacing); 112 113 label_ = gtk_label_new(NULL); 114 gtk_util::SetLabelColor(label_, &kLabelTextColor); 115 gtk_box_pack_start(GTK_BOX(vbox), label_, FALSE, FALSE, 0); 116 117 // The icon with a some padding on the left and right. 118 GtkWidget* icon_container = gtk_alignment_new(0, 0, 0, 0); 119 gtk_alignment_set_padding(GTK_ALIGNMENT(icon_container), 0, 0, 120 kIconHorizontalPadding, kIconHorizontalPadding); 121 icon_ = gtk_image_new(); 122 gtk_container_add(GTK_CONTAINER(icon_container), icon_); 123 gtk_box_pack_start(GTK_BOX(vbox), icon_container, FALSE, FALSE, 0); 124 125 GtkWidget* button_bar = gtk_hbox_new(FALSE, kButtonBarHorizontalSpacing); 126 gtk_box_pack_start(GTK_BOX(vbox), button_bar, FALSE, FALSE, 0); 127 128 GtkWidget* cancel_button = gtk_button_new_with_label( 129 l10n_util::GetStringUTF8(IDS_CANCEL).c_str()); 130 gtk_box_pack_start(GTK_BOX(button_bar), cancel_button, TRUE, FALSE, 0); 131 g_signal_connect(cancel_button, "clicked", 132 G_CALLBACK(&OnCancelClickedThunk), this); 133 134 try_again_button_ = gtk_button_new_with_label( 135 l10n_util::GetStringUTF8(IDS_SPEECH_INPUT_TRY_AGAIN).c_str()); 136 gtk_box_pack_start(GTK_BOX(button_bar), try_again_button_, TRUE, FALSE, 0); 137 g_signal_connect(try_again_button_, "clicked", 138 G_CALLBACK(&OnTryAgainClickedThunk), this); 139 140 GtkWidget* content = gtk_alignment_new(0, 0, 0, 0); 141 gtk_alignment_set_padding(GTK_ALIGNMENT(content), 142 kBubbleControlVerticalSpacing, kBubbleControlVerticalSpacing, 143 kBubbleControlHorizontalSpacing, kBubbleControlHorizontalSpacing); 144 gtk_container_add(GTK_CONTAINER(content), vbox); 145 146 GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom( 147 tab_contents()->profile()); 148 gfx::Rect rect(element_rect_.x() + kBubbleTargetOffsetX, 149 element_rect_.y() + element_rect_.height(), 1, 1); 150 info_bubble_ = InfoBubbleGtk::Show(tab_contents()->GetNativeView(), 151 &rect, 152 content, 153 InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT, 154 false, // match_system_theme 155 true, // grab_input 156 theme_provider, 157 this); 158 159 UpdateLayout(); 160} 161 162void SpeechInputBubbleGtk::Hide() { 163 if (info_bubble_) 164 info_bubble_->Close(); 165} 166 167void SpeechInputBubbleGtk::UpdateLayout() { 168 if (!info_bubble_) 169 return; 170 171 if (display_mode() == DISPLAY_MODE_MESSAGE) { 172 // Message text and the Try Again + Cancel buttons are visible, hide the 173 // icon. 174 gtk_label_set_text(GTK_LABEL(label_), 175 UTF16ToUTF8(message_text()).c_str()); 176 gtk_widget_show(try_again_button_); 177 gtk_widget_hide(icon_); 178 } else { 179 // Heading text, icon and cancel button are visible, hide the Try Again 180 // button. 181 if (display_mode() == DISPLAY_MODE_RECORDING) { 182 gtk_label_set_text(GTK_LABEL(label_), 183 l10n_util::GetStringUTF8(IDS_SPEECH_INPUT_BUBBLE_HEADING).c_str()); 184 SkBitmap* image = ResourceBundle::GetSharedInstance().GetBitmapNamed( 185 IDR_SPEECH_INPUT_MIC_EMPTY); 186 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(image); 187 gtk_image_set_from_pixbuf(GTK_IMAGE(icon_), pixbuf); 188 g_object_unref(pixbuf); 189 } else { 190 gtk_label_set_text(GTK_LABEL(label_), 191 l10n_util::GetStringUTF8(IDS_SPEECH_INPUT_BUBBLE_WORKING).c_str()); 192 } 193 gtk_widget_show(icon_); 194 gtk_widget_hide(try_again_button_); 195 } 196} 197 198void SpeechInputBubbleGtk::SetImage(const SkBitmap& image) { 199 if (image.isNull()) 200 return; 201 202 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&image); 203 gtk_image_set_from_pixbuf(GTK_IMAGE(icon_), pixbuf); 204 g_object_unref(pixbuf); 205} 206 207} // namespace 208 209SpeechInputBubble* SpeechInputBubble::CreateNativeBubble( 210 TabContents* tab_contents, 211 Delegate* delegate, 212 const gfx::Rect& element_rect) { 213 return new SpeechInputBubbleGtk(tab_contents, delegate, element_rect); 214} 215 216