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