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.
5 #include "chrome/browser/ui/gtk/edit_search_engine_dialog.h"
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"
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");
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
]);
67 EditSearchEngineDialog::EditSearchEngineDialog(
68 GtkWindow
* parent_window
,
69 TemplateURL
* template_url
,
70 EditSearchEngineControllerDelegate
* delegate
,
72 : controller_(new EditSearchEngineController(template_url
, delegate
,
74 Init(parent_window
, profile
);
77 EditSearchEngineDialog::~EditSearchEngineDialog() {}
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
);
85 dialog_
= gtk_dialog_new_with_buttons(
88 static_cast<GtkDialogFlags
>(GTK_DIALOG_MODAL
| GTK_DIALOG_NO_SEPARATOR
),
93 ok_button_
= gtk_dialog_add_button(GTK_DIALOG(dialog_
),
94 controller_
->template_url() ?
98 gtk_dialog_set_default_response(GTK_DIALOG(dialog_
), GTK_RESPONSE_OK
);
100 // The dialog layout hierarchy looks like this:
102 // \ GtkVBox |dialog_->vbox|
103 // +-\ GtkTable |controls|
107 // | | +- GtkEntry |title_entry_|
108 // | | +- GtkImage |title_image_|
112 // | | +- GtkEntry |keyword_entry_|
113 // | | +- GtkImage |keyword_image_|
117 // | +- GtkEntry |url_entry_|
118 // | +- GtkImage |url_image_|
119 // +- GtkLabel |description_label|
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);
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
);
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);
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
);
142 if (controller_
->template_url()) {
144 GTK_ENTRY(title_entry_
),
145 base::UTF16ToUTF8(controller_
->template_url()->short_name()).c_str());
147 GTK_ENTRY(keyword_entry_
),
148 base::UTF16ToUTF8(controller_
->template_url()->keyword()).c_str());
150 GTK_ENTRY(url_entry_
),
151 base::UTF16ToUTF8(controller_
->template_url()->url_ref().DisplayURL()).
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);
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
);
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_
),
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);
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
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(),
203 GtkWidget
* description_label
= gtk_label_new(description
.c_str());
204 gtk_box_pack_start(GTK_BOX(content_area
), description_label
,
207 gtk_box_set_spacing(GTK_BOX(content_area
), ui::kContentAreaSpacing
);
211 gtk_util::ShowDialog(dialog_
);
213 g_signal_connect(dialog_
, "response", G_CALLBACK(OnResponseThunk
), this);
214 g_signal_connect(dialog_
, "destroy", G_CALLBACK(OnWindowDestroyThunk
), this);
217 base::string16
EditSearchEngineDialog::GetTitleInput() const {
218 return base::UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(title_entry_
)));
221 base::string16
EditSearchEngineDialog::GetKeywordInput() const {
222 return base::UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(keyword_entry_
)));
225 std::string
EditSearchEngineDialog::GetURLInput() const {
226 return gtk_entry_get_text(GTK_ENTRY(url_entry_
));
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
);
242 void EditSearchEngineDialog::UpdateImage(GtkWidget
* image
,
244 int invalid_message_id
) {
245 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
247 gtk_widget_set_has_tooltip(image
, FALSE
);
248 gtk_image_set_from_pixbuf(GTK_IMAGE(image
),
249 rb
.GetNativeImageNamed(IDR_INPUT_GOOD
).ToGdkPixbuf());
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());
258 void EditSearchEngineDialog::OnEntryChanged(GtkEditable
* editable
) {
262 void EditSearchEngineDialog::OnResponse(GtkWidget
* dialog
, int response_id
) {
263 if (response_id
== GTK_RESPONSE_OK
) {
264 controller_
->AcceptAddOrEdit(GetTitleInput(),
268 controller_
->CleanUpCancelledAdd();
270 gtk_widget_destroy(dialog_
);
273 void EditSearchEngineDialog::OnWindowDestroy(GtkWidget
* widget
) {
274 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);