Home | History | Annotate | Download | only in gtk
      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/ui/gtk/edit_search_engine_dialog.h"
      6 
      7 #include <gtk/gtk.h>
      8 
      9 #include "base/i18n/rtl.h"
     10 #include "base/message_loop.h"
     11 #include "base/utf_string_conversions.h"
     12 #include "chrome/browser/net/url_fixer_upper.h"
     13 #include "chrome/browser/profiles/profile.h"
     14 #include "chrome/browser/search_engines/template_url.h"
     15 #include "chrome/browser/search_engines/template_url_model.h"
     16 #include "chrome/browser/ui/gtk/gtk_util.h"
     17 #include "chrome/browser/ui/search_engines/edit_search_engine_controller.h"
     18 #include "googleurl/src/gurl.h"
     19 #include "grit/app_resources.h"
     20 #include "grit/generated_resources.h"
     21 #include "grit/theme_resources.h"
     22 #include "ui/base/l10n/l10n_util.h"
     23 #include "ui/base/resource/resource_bundle.h"
     24 
     25 namespace {
     26 
     27 std::string GetDisplayURL(const TemplateURL& turl) {
     28   return turl.url() ? UTF16ToUTF8(turl.url()->DisplayURL()) : std::string();
     29 }
     30 
     31 // Forces text to lowercase when connected to an editable's "insert-text"
     32 // signal.  (Like views Textfield::STYLE_LOWERCASE.)
     33 void LowercaseInsertTextHandler(GtkEditable *editable, const gchar *text,
     34                                 gint length, gint *position, gpointer data) {
     35   string16 original_text = UTF8ToUTF16(text);
     36   string16 lower_text = l10n_util::ToLower(original_text);
     37   if (lower_text != original_text) {
     38     std::string result = UTF16ToUTF8(lower_text);
     39     // Prevent ourselves getting called recursively about our own edit.
     40     g_signal_handlers_block_by_func(G_OBJECT(editable),
     41         reinterpret_cast<gpointer>(LowercaseInsertTextHandler), data);
     42     gtk_editable_insert_text(editable, result.c_str(), result.size(), position);
     43     g_signal_handlers_unblock_by_func(G_OBJECT(editable),
     44         reinterpret_cast<gpointer>(LowercaseInsertTextHandler), data);
     45     // We've inserted our modified version, stop the defalut handler from
     46     // inserting the original.
     47     g_signal_stop_emission_by_name(G_OBJECT(editable), "insert_text");
     48   }
     49 }
     50 
     51 void SetWidgetStyle(GtkWidget* entry, GtkStyle* label_style,
     52                     GtkStyle* dialog_style) {
     53   gtk_widget_modify_fg(entry, GTK_STATE_NORMAL,
     54                        &label_style->fg[GTK_STATE_NORMAL]);
     55   gtk_widget_modify_fg(entry, GTK_STATE_INSENSITIVE,
     56                        &label_style->fg[GTK_STATE_INSENSITIVE]);
     57   // GTK_NO_WINDOW widgets like GtkLabel don't draw their own background, so we
     58   // combine the normal or insensitive foreground of the label style with the
     59   // normal background of the window style to achieve the "normal label" and
     60   // "insensitive label" colors.
     61   gtk_widget_modify_base(entry, GTK_STATE_NORMAL,
     62                          &dialog_style->bg[GTK_STATE_NORMAL]);
     63   gtk_widget_modify_base(entry, GTK_STATE_INSENSITIVE,
     64                          &dialog_style->bg[GTK_STATE_NORMAL]);
     65 }
     66 
     67 }  // namespace
     68 
     69 EditSearchEngineDialog::EditSearchEngineDialog(
     70     GtkWindow* parent_window,
     71     const TemplateURL* template_url,
     72     EditSearchEngineControllerDelegate* delegate,
     73     Profile* profile)
     74     : controller_(new EditSearchEngineController(template_url, delegate,
     75                                                  profile)) {
     76   Init(parent_window, profile);
     77 }
     78 
     79 EditSearchEngineDialog::~EditSearchEngineDialog() {}
     80 
     81 void EditSearchEngineDialog::Init(GtkWindow* parent_window, Profile* profile) {
     82   std::string dialog_name = l10n_util::GetStringUTF8(
     83       controller_->template_url() ?
     84       IDS_SEARCH_ENGINES_EDITOR_EDIT_WINDOW_TITLE :
     85       IDS_SEARCH_ENGINES_EDITOR_NEW_WINDOW_TITLE);
     86 
     87   dialog_ = gtk_dialog_new_with_buttons(
     88       dialog_name.c_str(),
     89       parent_window,
     90       static_cast<GtkDialogFlags>(GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR),
     91       GTK_STOCK_CANCEL,
     92       GTK_RESPONSE_CANCEL,
     93       NULL);
     94 
     95   ok_button_ = gtk_dialog_add_button(GTK_DIALOG(dialog_),
     96                                      controller_->template_url() ?
     97                                      GTK_STOCK_SAVE :
     98                                      GTK_STOCK_ADD,
     99                                      GTK_RESPONSE_OK);
    100   gtk_dialog_set_default_response(GTK_DIALOG(dialog_), GTK_RESPONSE_OK);
    101 
    102   // The dialog layout hierarchy looks like this:
    103   //
    104   // \ GtkVBox |dialog_->vbox|
    105   // +-\ GtkTable |controls|
    106   // | +-\ row 0
    107   // | | +- GtkLabel
    108   // | | +-\ GtkHBox
    109   // | |   +- GtkEntry |title_entry_|
    110   // | |   +- GtkImage |title_image_|
    111   // | +-\ row 1
    112   // | | +- GtkLabel
    113   // | | +-\ GtkHBox
    114   // | |   +- GtkEntry |keyword_entry_|
    115   // | |   +- GtkImage |keyword_image_|
    116   // | +-\ row 2
    117   // |   +- GtkLabel
    118   // |   +-\ GtkHBox
    119   // |     +- GtkEntry |url_entry_|
    120   // |     +- GtkImage |url_image_|
    121   // +- GtkLabel |description_label|
    122 
    123   title_entry_ = gtk_entry_new();
    124   gtk_entry_set_activates_default(GTK_ENTRY(title_entry_), TRUE);
    125   g_signal_connect(title_entry_, "changed",
    126                    G_CALLBACK(OnEntryChangedThunk), this);
    127 
    128   keyword_entry_ = gtk_entry_new();
    129   gtk_entry_set_activates_default(GTK_ENTRY(keyword_entry_), TRUE);
    130   g_signal_connect(keyword_entry_, "changed",
    131                    G_CALLBACK(OnEntryChangedThunk), this);
    132   g_signal_connect(keyword_entry_, "insert-text",
    133                    G_CALLBACK(LowercaseInsertTextHandler), NULL);
    134 
    135   url_entry_ = gtk_entry_new();
    136   gtk_entry_set_activates_default(GTK_ENTRY(url_entry_), TRUE);
    137   g_signal_connect(url_entry_, "changed",
    138                    G_CALLBACK(OnEntryChangedThunk), this);
    139 
    140   title_image_ = gtk_image_new_from_pixbuf(NULL);
    141   keyword_image_ = gtk_image_new_from_pixbuf(NULL);
    142   url_image_ = gtk_image_new_from_pixbuf(NULL);
    143 
    144   if (controller_->template_url()) {
    145     gtk_entry_set_text(
    146         GTK_ENTRY(title_entry_),
    147         UTF16ToUTF8(controller_->template_url()->short_name()).c_str());
    148     gtk_entry_set_text(
    149         GTK_ENTRY(keyword_entry_),
    150         UTF16ToUTF8(controller_->template_url()->keyword()).c_str());
    151     gtk_entry_set_text(
    152         GTK_ENTRY(url_entry_),
    153         GetDisplayURL(*controller_->template_url()).c_str());
    154     // We don't allow users to edit prepopulated URLs.
    155     gtk_editable_set_editable(
    156         GTK_EDITABLE(url_entry_),
    157         controller_->template_url()->prepopulate_id() == 0);
    158 
    159     if (controller_->template_url()->prepopulate_id() != 0) {
    160       GtkWidget* fake_label = gtk_label_new("Fake label");
    161       gtk_widget_set_sensitive(fake_label,
    162           controller_->template_url()->prepopulate_id() == 0);
    163       GtkStyle* label_style = gtk_widget_get_style(fake_label);
    164       GtkStyle* dialog_style = gtk_widget_get_style(dialog_);
    165       SetWidgetStyle(url_entry_, label_style, dialog_style);
    166       gtk_widget_destroy(fake_label);
    167     }
    168   }
    169 
    170   GtkWidget* controls = gtk_util::CreateLabeledControlsGroup(NULL,
    171       l10n_util::GetStringUTF8(
    172           IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_LABEL).c_str(),
    173       gtk_util::CreateEntryImageHBox(title_entry_, title_image_),
    174       l10n_util::GetStringUTF8(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_LABEL).c_str(),
    175       gtk_util::CreateEntryImageHBox(keyword_entry_, keyword_image_),
    176       l10n_util::GetStringUTF8(IDS_SEARCH_ENGINES_EDITOR_URL_LABEL).c_str(),
    177       gtk_util::CreateEntryImageHBox(url_entry_, url_image_),
    178       NULL);
    179   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_)->vbox), controls,
    180                      FALSE, FALSE, 0);
    181 
    182   // On RTL UIs (such as Arabic and Hebrew) the description text is not
    183   // displayed correctly since it contains the substring "%s". This substring
    184   // is not interpreted by the Unicode BiDi algorithm as an LTR string and
    185   // therefore the end result is that the following right to left text is
    186   // displayed: ".three two s% one" (where 'one', 'two', etc. are words in
    187   // Hebrew).
    188   //
    189   // In order to fix this problem we transform the substring "%s" so that it
    190   // is displayed correctly when rendered in an RTL context.
    191   std::string description =
    192       l10n_util::GetStringUTF8(IDS_SEARCH_ENGINES_EDITOR_URL_DESCRIPTION_LABEL);
    193   if (base::i18n::IsRTL()) {
    194     const std::string reversed_percent("s%");
    195     std::string::size_type percent_index = description.find("%s");
    196     if (percent_index != std::string::npos) {
    197       description.replace(percent_index,
    198                           reversed_percent.length(),
    199                           reversed_percent);
    200     }
    201   }
    202 
    203   GtkWidget* description_label = gtk_label_new(description.c_str());
    204   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_)->vbox), description_label,
    205                      FALSE, FALSE, 0);
    206 
    207   gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog_)->vbox),
    208                       gtk_util::kContentAreaSpacing);
    209 
    210   EnableControls();
    211 
    212   gtk_util::ShowDialog(dialog_);
    213 
    214   g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
    215   g_signal_connect(dialog_, "destroy", G_CALLBACK(OnWindowDestroyThunk), this);
    216 }
    217 
    218 string16 EditSearchEngineDialog::GetTitleInput() const {
    219   return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(title_entry_)));
    220 }
    221 
    222 string16 EditSearchEngineDialog::GetKeywordInput() const {
    223   return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(keyword_entry_)));
    224 }
    225 
    226 std::string EditSearchEngineDialog::GetURLInput() const {
    227   return gtk_entry_get_text(GTK_ENTRY(url_entry_));
    228 }
    229 
    230 void EditSearchEngineDialog::EnableControls() {
    231   gtk_widget_set_sensitive(ok_button_,
    232                            controller_->IsKeywordValid(GetKeywordInput()) &&
    233                            controller_->IsTitleValid(GetTitleInput()) &&
    234                            controller_->IsURLValid(GetURLInput()));
    235   UpdateImage(keyword_image_, controller_->IsKeywordValid(GetKeywordInput()),
    236               IDS_SEARCH_ENGINES_INVALID_KEYWORD_TT);
    237   UpdateImage(url_image_, controller_->IsURLValid(GetURLInput()),
    238               IDS_SEARCH_ENGINES_INVALID_URL_TT);
    239   UpdateImage(title_image_, controller_->IsTitleValid(GetTitleInput()),
    240               IDS_SEARCH_ENGINES_INVALID_TITLE_TT);
    241 }
    242 
    243 void EditSearchEngineDialog::UpdateImage(GtkWidget* image,
    244                                          bool is_valid,
    245                                          int invalid_message_id) {
    246   if (is_valid) {
    247     gtk_widget_set_has_tooltip(image, FALSE);
    248     gtk_image_set_from_pixbuf(GTK_IMAGE(image),
    249         ResourceBundle::GetSharedInstance().GetPixbufNamed(
    250             IDR_INPUT_GOOD));
    251   } else {
    252     gtk_widget_set_tooltip_text(
    253         image, l10n_util::GetStringUTF8(invalid_message_id).c_str());
    254     gtk_image_set_from_pixbuf(GTK_IMAGE(image),
    255         ResourceBundle::GetSharedInstance().GetPixbufNamed(
    256             IDR_INPUT_ALERT));
    257   }
    258 }
    259 
    260 void EditSearchEngineDialog::OnEntryChanged(GtkEditable* editable) {
    261   EnableControls();
    262 }
    263 
    264 void EditSearchEngineDialog::OnResponse(GtkWidget* dialog, int response_id) {
    265   if (response_id == GTK_RESPONSE_OK) {
    266     controller_->AcceptAddOrEdit(GetTitleInput(),
    267                                  GetKeywordInput(),
    268                                  GetURLInput());
    269   } else {
    270     controller_->CleanUpCancelledAdd();
    271   }
    272   gtk_widget_destroy(dialog_);
    273 }
    274 
    275 void EditSearchEngineDialog::OnWindowDestroy(GtkWidget* widget) {
    276   MessageLoop::current()->DeleteSoon(FROM_HERE, this);
    277 }
    278