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