// 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/gtk/translate_infobars.h" #include #include #include "app/l10n_util.h" #include "base/utf_string_conversions.h" #include "chrome/app/chrome_dll_resource.h" #include "chrome/browser/gtk/gtk_util.h" #include "chrome/browser/gtk/menu_gtk.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/translate/translate_infobars_delegates.h" #include "chrome/browser/translate/options_menu_model.h" #include "chrome/common/notification_service.h" #include "grit/app_resources.h" #include "grit/generated_resources.h" #include "grit/locale_settings.h" #include "grit/theme_resources.h" namespace { // Reorders the widgets in the NULL terminated array |widgets| that are all // children of |box|. void ReorderWidgetsTo(GtkWidget* box, GtkWidget** widgets) { for (int count = 0; *widgets != NULL; widgets++) { gtk_box_reorder_child(GTK_BOX(box), *widgets, count++); gtk_widget_show_all(*widgets); } } // Creates a combobox set up to display text from a list of language codes // (translating the codes into the display string). GtkWidget* BuildLanguageComboboxFrom( TranslateInfoBarDelegate* delegate, const std::vector& languages) { GtkListStore* model = gtk_list_store_new(1, G_TYPE_STRING); for (std::vector::const_iterator iter = languages.begin(); iter != languages.end(); ++iter) { GtkTreeIter tree_iter; std::string name = UTF16ToUTF8(delegate->GetDisplayNameForLocale(*iter)); gtk_list_store_append(model, &tree_iter); gtk_list_store_set(model, &tree_iter, 0, name.c_str(), -1); } GtkWidget* combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model)); g_object_unref(model); GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(combobox), renderer, "text", 0, NULL); return combobox; } // Builds a button with an arrow in it to emulate the menu-button style from // the windows version. GtkWidget* BuildOptionsMenuButton() { GtkWidget* button = gtk_button_new(); GtkWidget* former_child = gtk_bin_get_child(GTK_BIN(button)); if (former_child) gtk_container_remove(GTK_CONTAINER(button), former_child); GtkWidget* hbox = gtk_hbox_new(FALSE, 0); GtkWidget* label = gtk_label_new( l10n_util::GetStringUTF8(IDS_TRANSLATE_INFOBAR_OPTIONS).c_str()); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); GtkWidget* arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE); gtk_box_pack_start(GTK_BOX(hbox), arrow, FALSE, FALSE, 0); gtk_container_add(GTK_CONTAINER(button), hbox); return button; } } // namespace TranslateInfoBar::TranslateInfoBar(TranslateInfoBarDelegate* delegate) : InfoBar(delegate), swapped_language_placeholders_(false) { BuildWidgets(); // Register for PAGE_TRANSLATED notification. notification_registrar_.Add(this, NotificationType::PAGE_TRANSLATED, Source(GetDelegate()->tab_contents())); } TranslateInfoBar::~TranslateInfoBar() { } void TranslateInfoBar::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type.value == NotificationType::PAGE_TRANSLATED) { TabContents* tab = Source(source).ptr(); if (tab != GetDelegate()->tab_contents()) return; UpdateState(TranslateInfoBarDelegate::kAfterTranslate); } else { InfoBar::Observe(type, source, details); } } bool TranslateInfoBar::IsCommandIdChecked(int command_id) const { TranslateInfoBarDelegate* translate_delegate = GetDelegate(); switch (command_id) { case IDC_TRANSLATE_OPTIONS_NEVER_TRANSLATE_LANG : return translate_delegate->IsLanguageBlacklisted(); case IDC_TRANSLATE_OPTIONS_NEVER_TRANSLATE_SITE : return translate_delegate->IsSiteBlacklisted(); case IDC_TRANSLATE_OPTIONS_ALWAYS : return translate_delegate->ShouldAlwaysTranslate(); default: NOTREACHED() << "Invalid command_id from menu"; break; } return false; } bool TranslateInfoBar::IsCommandIdEnabled(int command_id) const { return true; } bool TranslateInfoBar::GetAcceleratorForCommandId(int command_id, menus::Accelerator* accelerator) { return false; } void TranslateInfoBar::ExecuteCommand(int command_id) { switch (command_id) { case IDC_TRANSLATE_OPTIONS_NEVER_TRANSLATE_LANG: GetDelegate()->ToggleLanguageBlacklist(); break; case IDC_TRANSLATE_OPTIONS_NEVER_TRANSLATE_SITE: GetDelegate()->ToggleSiteBlacklist(); break; case IDC_TRANSLATE_OPTIONS_ALWAYS: GetDelegate()->ToggleAlwaysTranslate(); break; case IDC_TRANSLATE_OPTIONS_ABOUT: { TabContents* tab_contents = GetDelegate()->tab_contents(); if (tab_contents) { string16 url = l10n_util::GetStringUTF16( IDS_ABOUT_GOOGLE_TRANSLATE_URL); tab_contents->OpenURL(GURL(url), GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK); } break; } default: NOTREACHED() << "Invalid command id from menu."; break; } } void TranslateInfoBar::BuildWidgets() { translate_box_ = gtk_hbox_new(FALSE, gtk_util::kControlSpacing); gtk_widget_set_no_show_all(translate_box_, TRUE); // Add all labels to the translate_box_. Unlike views or cocoa, there's no // concept of manual layout in GTK, so we'll manually reorder these widgets // inside |translate_box_| and selectively show and hide them. label_1_ = gtk_label_new(NULL); gtk_box_pack_start(GTK_BOX(translate_box_), label_1_, FALSE, FALSE, 0); label_2_ = gtk_label_new(NULL); gtk_box_pack_start(GTK_BOX(translate_box_), label_2_, FALSE, FALSE, 0); label_3_ = gtk_label_new(NULL); gtk_box_pack_start(GTK_BOX(translate_box_), label_3_, FALSE, FALSE, 0); translating_label_ = gtk_label_new( l10n_util::GetStringUTF8(IDS_TRANSLATE_INFOBAR_TRANSLATING).c_str()); gtk_box_pack_start(GTK_BOX(translate_box_), translating_label_, FALSE, FALSE, 0); accept_button_ = gtk_button_new_with_label( l10n_util::GetStringUTF8(IDS_TRANSLATE_INFOBAR_ACCEPT).c_str()); g_signal_connect(accept_button_, "clicked", G_CALLBACK(&OnAcceptPressedThunk), this); accept_button_vbox_ = gtk_util::CenterWidgetInHBox( translate_box_, accept_button_, false, 0); deny_button_ = gtk_button_new_with_label( l10n_util::GetStringUTF8(IDS_TRANSLATE_INFOBAR_DENY).c_str()); g_signal_connect(deny_button_, "clicked", G_CALLBACK(&OnDenyPressedThunk), this); deny_button_vbox_ = gtk_util::CenterWidgetInHBox( translate_box_, deny_button_, false, 0); std::vector orig_languages; GetDelegate()->GetAvailableOriginalLanguages(&orig_languages); original_language_combobox_ = BuildLanguageComboboxFrom(GetDelegate(), orig_languages); g_signal_connect(original_language_combobox_, "changed", G_CALLBACK(&OnOriginalModifiedThunk), this); original_language_combobox_vbox_ = gtk_util::CenterWidgetInHBox( translate_box_, original_language_combobox_, false, 0); std::vector target_languages; GetDelegate()->GetAvailableTargetLanguages(&target_languages); target_language_combobox_ = BuildLanguageComboboxFrom(GetDelegate(), target_languages); g_signal_connect(target_language_combobox_, "changed", G_CALLBACK(&OnTargetModifiedThunk), this); target_language_combobox_vbox_ = gtk_util::CenterWidgetInHBox( translate_box_, target_language_combobox_, false, 0); gtk_box_pack_start(GTK_BOX(hbox_), translate_box_, FALSE, FALSE, 0); // The options button sits outside the translate_box so that it can be end // packed in hbox_. options_menu_button_ = BuildOptionsMenuButton(); g_signal_connect(options_menu_button_, "clicked", G_CALLBACK(&OnOptionsClickedThunk), this); gtk_widget_show_all(options_menu_button_); gtk_util::CenterWidgetInHBox( hbox_, options_menu_button_, true, 0); UpdateState(GetDelegate()->state()); gtk_widget_show_all(border_bin_.get()); } void TranslateInfoBar::UpdateState( TranslateInfoBarDelegate::TranslateState new_state) { // Show the box... gtk_widget_show(translate_box_); // ...but hide all children of the translate box. They will be selectively // unhidden and reordered based on the current translate state. gtk_container_foreach( GTK_CONTAINER(translate_box_), reinterpret_cast(gtk_widget_hide_all), NULL); switch (new_state) { case TranslateInfoBarDelegate::kBeforeTranslate: { SetLabels(); GtkWidget* before_state[] = { label_1_, original_language_combobox_vbox_, label_2_, accept_button_vbox_, deny_button_vbox_, NULL }; ReorderWidgetsTo(translate_box_, before_state); gtk_combo_box_set_active(GTK_COMBO_BOX(original_language_combobox_), GetDelegate()->original_lang_index()); break; } case TranslateInfoBarDelegate::kTranslating: { gtk_widget_show(translating_label_); break; } case TranslateInfoBarDelegate::kAfterTranslate: { SetLabels(); GtkWidget* first_button_box = swapped_language_placeholders_ ? target_language_combobox_vbox_ : original_language_combobox_vbox_; GtkWidget* second_button_box = swapped_language_placeholders_ ? original_language_combobox_vbox_ : target_language_combobox_vbox_; GtkWidget* after_state[] = { label_1_, first_button_box, label_2_, second_button_box, label_3_, NULL }; ReorderWidgetsTo(translate_box_, after_state); gtk_combo_box_set_active(GTK_COMBO_BOX(original_language_combobox_), GetDelegate()->original_lang_index()); gtk_combo_box_set_active(GTK_COMBO_BOX(target_language_combobox_), GetDelegate()->target_lang_index()); break; } default: { NOTIMPLEMENTED() << "Received state " << new_state; } } // Clear options menu model so that it'll be created for new state. options_menu_model_.reset(); } void TranslateInfoBar::SetLabels() { std::vector offsets; string16 message_text_utf16; GetDelegate()->GetMessageText(&message_text_utf16, &offsets, &swapped_language_placeholders_); string16 text = message_text_utf16.substr(0, offsets[0]); gtk_label_set_text(GTK_LABEL(label_1_), UTF16ToUTF8(text).c_str()); text = message_text_utf16.substr(offsets[0], offsets[1] - offsets[0]); gtk_label_set_text(GTK_LABEL(label_2_), UTF16ToUTF8(text).c_str()); if (offsets.size() == 3) { text = message_text_utf16.substr(offsets[1], offsets[2] - offsets[1]); gtk_label_set_text(GTK_LABEL(label_3_), UTF16ToUTF8(text).c_str()); } } TranslateInfoBarDelegate* TranslateInfoBar::GetDelegate() const { return static_cast(delegate()); } void TranslateInfoBar::LanguageModified() { options_menu_model_.reset(); // Selecting an item from the "from language" menu in the before translate // phase shouldn't trigger translation - http://crbug.com/36666 if (GetDelegate()->state() == TranslateInfoBarDelegate::kAfterTranslate) GetDelegate()->Translate(); } void TranslateInfoBar::OnOriginalModified(GtkWidget* sender) { int index = gtk_combo_box_get_active(GTK_COMBO_BOX(sender)); if (index == GetDelegate()->original_lang_index()) return; GetDelegate()->ModifyOriginalLanguage(index); LanguageModified(); } void TranslateInfoBar::OnTargetModified(GtkWidget* sender) { int index = gtk_combo_box_get_active(GTK_COMBO_BOX(sender)); if (index == GetDelegate()->target_lang_index()) return; GetDelegate()->ModifyTargetLanguage(index); LanguageModified(); } void TranslateInfoBar::OnAcceptPressed(GtkWidget* sender) { GetDelegate()->Translate(); UpdateState(GetDelegate()->state()); } void TranslateInfoBar::OnDenyPressed(GtkWidget* sender) { GetDelegate()->TranslationDeclined(); RemoveInfoBar(); } void TranslateInfoBar::OnOptionsClicked(GtkWidget* sender) { if (!options_menu_model_.get()) { options_menu_model_.reset(new OptionsMenuModel(this, GetDelegate())); options_menu_menu_.reset(new MenuGtk(this, options_menu_model_.get())); } options_menu_menu_->Popup(sender, 1, gtk_get_current_event_time()); } // TranslateInfoBarDelegate, InfoBarDelegate overrides: ------------------ InfoBar* TranslateInfoBarDelegate::CreateInfoBar() { return new TranslateInfoBar(this); }