• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetDisplayURL(const TemplateURL & turl)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.)
LowercaseInsertTextHandler(GtkEditable * editable,const gchar * text,gint length,gint * position,gpointer data)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 
SetWidgetStyle(GtkWidget * entry,GtkStyle * label_style,GtkStyle * dialog_style)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 
EditSearchEngineDialog(GtkWindow * parent_window,const TemplateURL * template_url,EditSearchEngineControllerDelegate * delegate,Profile * profile)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 
~EditSearchEngineDialog()79 EditSearchEngineDialog::~EditSearchEngineDialog() {}
80 
Init(GtkWindow * parent_window,Profile * profile)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 
GetTitleInput() const218 string16 EditSearchEngineDialog::GetTitleInput() const {
219   return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(title_entry_)));
220 }
221 
GetKeywordInput() const222 string16 EditSearchEngineDialog::GetKeywordInput() const {
223   return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(keyword_entry_)));
224 }
225 
GetURLInput() const226 std::string EditSearchEngineDialog::GetURLInput() const {
227   return gtk_entry_get_text(GTK_ENTRY(url_entry_));
228 }
229 
EnableControls()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 
UpdateImage(GtkWidget * image,bool is_valid,int invalid_message_id)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 
OnEntryChanged(GtkEditable * editable)260 void EditSearchEngineDialog::OnEntryChanged(GtkEditable* editable) {
261   EnableControls();
262 }
263 
OnResponse(GtkWidget * dialog,int response_id)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 
OnWindowDestroy(GtkWidget * widget)275 void EditSearchEngineDialog::OnWindowDestroy(GtkWidget* widget) {
276   MessageLoop::current()->DeleteSoon(FROM_HERE, this);
277 }
278