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/views/edit_search_engine_dialog.h"
7 #include "base/i18n/case_conversion.h"
8 #include "base/i18n/rtl.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
12 #include "chrome/browser/ui/search_engines/edit_search_engine_controller.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "components/constrained_window/constrained_window_views.h"
15 #include "components/search_engines/template_url.h"
16 #include "grit/theme_resources.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/events/event.h"
20 #include "ui/views/controls/image_view.h"
21 #include "ui/views/controls/label.h"
22 #include "ui/views/controls/textfield/textfield.h"
23 #include "ui/views/layout/grid_layout.h"
24 #include "ui/views/layout/layout_constants.h"
25 #include "ui/views/widget/widget.h"
26 #include "ui/views/window/dialog_client_view.h"
29 using views::GridLayout
;
30 using views::Textfield
;
34 void EditSearchEngine(gfx::NativeWindow parent
,
35 TemplateURL
* template_url
,
36 EditSearchEngineControllerDelegate
* delegate
,
38 EditSearchEngineDialog::Show(parent
, template_url
, delegate
, profile
);
43 EditSearchEngineDialog::EditSearchEngineDialog(
44 TemplateURL
* template_url
,
45 EditSearchEngineControllerDelegate
* delegate
,
47 : controller_(new EditSearchEngineController(template_url
,
53 EditSearchEngineDialog::~EditSearchEngineDialog() {
57 void EditSearchEngineDialog::Show(gfx::NativeWindow parent
,
58 TemplateURL
* template_url
,
59 EditSearchEngineControllerDelegate
* delegate
,
61 EditSearchEngineDialog
* contents
=
62 new EditSearchEngineDialog(template_url
, delegate
, profile
);
63 // Window interprets an empty rectangle as needing to query the content for
64 // the size as well as centering relative to the parent.
65 constrained_window::CreateBrowserModalDialogViews(contents
, parent
);
66 contents
->GetWidget()->Show();
67 contents
->GetDialogClientView()->UpdateDialogButtons();
68 contents
->title_tf_
->SelectAll(true);
69 contents
->title_tf_
->RequestFocus();
72 ui::ModalType
EditSearchEngineDialog::GetModalType() const {
73 return ui::MODAL_TYPE_WINDOW
;
76 base::string16
EditSearchEngineDialog::GetWindowTitle() const {
77 return l10n_util::GetStringUTF16(controller_
->template_url() ?
78 IDS_SEARCH_ENGINES_EDITOR_EDIT_WINDOW_TITLE
:
79 IDS_SEARCH_ENGINES_EDITOR_NEW_WINDOW_TITLE
);
82 bool EditSearchEngineDialog::IsDialogButtonEnabled(
83 ui::DialogButton button
) const {
84 if (button
== ui::DIALOG_BUTTON_OK
) {
85 return (controller_
->IsKeywordValid(keyword_tf_
->text()) &&
86 controller_
->IsTitleValid(title_tf_
->text()) &&
87 controller_
->IsURLValid(base::UTF16ToUTF8(url_tf_
->text())));
92 bool EditSearchEngineDialog::Cancel() {
93 controller_
->CleanUpCancelledAdd();
97 bool EditSearchEngineDialog::Accept() {
98 controller_
->AcceptAddOrEdit(title_tf_
->text(), keyword_tf_
->text(),
99 base::UTF16ToUTF8(url_tf_
->text()));
103 void EditSearchEngineDialog::ContentsChanged(
105 const base::string16
& new_contents
) {
106 // Force the keyword text to be lowercase, keep the caret or selection.
107 if (sender
== keyword_tf_
&& !sender
->HasCompositionText()) {
108 // TODO(msw): Prevent textfield scrolling with selection model changes here.
109 const gfx::SelectionModel selection_model
= sender
->GetSelectionModel();
110 sender
->SetText(base::i18n::ToLower(new_contents
));
111 sender
->SelectSelectionModel(selection_model
);
114 GetDialogClientView()->UpdateDialogButtons();
118 bool EditSearchEngineDialog::HandleKeyEvent(
120 const ui::KeyEvent
& key_event
) {
124 void EditSearchEngineDialog::Init() {
125 // Create the views we'll need.
126 if (controller_
->template_url()) {
127 title_tf_
= CreateTextfield(controller_
->template_url()->short_name());
128 keyword_tf_
= CreateTextfield(controller_
->template_url()->keyword());
129 url_tf_
= CreateTextfield(
130 controller_
->template_url()->url_ref().DisplayURL(
131 UIThreadSearchTermsData(controller_
->profile())));
132 // We don't allow users to edit prepopulate URLs. This is done as
133 // occasionally we need to update the URL of prepopulated TemplateURLs.
134 url_tf_
->SetReadOnly(controller_
->template_url()->prepopulate_id() != 0);
136 title_tf_
= CreateTextfield(base::string16());
137 keyword_tf_
= CreateTextfield(base::string16());
138 url_tf_
= CreateTextfield(base::string16());
140 title_iv_
= new views::ImageView();
141 keyword_iv_
= new views::ImageView();
142 url_iv_
= new views::ImageView();
146 const int related_x
= views::kRelatedControlHorizontalSpacing
;
147 const int related_y
= views::kRelatedControlVerticalSpacing
;
148 const int unrelated_y
= views::kUnrelatedControlVerticalSpacing
;
150 // View and GridLayout take care of deleting GridLayout for us.
151 GridLayout
* layout
= GridLayout::CreatePanel(this);
152 SetLayoutManager(layout
);
154 // Define the structure of the layout.
157 views::ColumnSet
* column_set
= layout
->AddColumnSet(0);
158 column_set
->AddPaddingColumn(1, 0);
159 column_set
->AddColumn(GridLayout::FILL
, GridLayout::CENTER
, 0,
160 GridLayout::USE_PREF
, 0, 0);
161 column_set
->AddPaddingColumn(0, related_x
);
162 column_set
->AddColumn(GridLayout::FILL
, GridLayout::CENTER
, 0,
163 GridLayout::USE_PREF
, 0, 0);
164 column_set
->LinkColumnSizes(1, 3, -1);
166 // For the Textfields.
167 column_set
= layout
->AddColumnSet(1);
168 column_set
->AddColumn(GridLayout::LEADING
, GridLayout::CENTER
, 0,
169 GridLayout::USE_PREF
, 0, 0);
170 column_set
->AddPaddingColumn(0, related_x
);
171 column_set
->AddColumn(GridLayout::FILL
, GridLayout::CENTER
, 1,
172 GridLayout::USE_PREF
, 0, 0);
173 column_set
->AddPaddingColumn(0, related_x
);
174 column_set
->AddColumn(GridLayout::CENTER
, GridLayout::CENTER
, 0,
175 GridLayout::USE_PREF
, 0, 0);
177 // For the description.
178 column_set
= layout
->AddColumnSet(2);
179 column_set
->AddColumn(GridLayout::FILL
, GridLayout::CENTER
, 0,
180 GridLayout::USE_PREF
, 0, 0);
183 layout
->StartRow(0, 1);
184 layout
->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_LABEL
));
185 layout
->AddView(title_tf_
);
186 layout
->AddView(title_iv_
);
188 layout
->StartRowWithPadding(0, 1, 0, related_y
);
189 layout
->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_LABEL
));
190 layout
->AddView(keyword_tf_
);
191 layout
->AddView(keyword_iv_
);
193 layout
->StartRowWithPadding(0, 1, 0, related_y
);
194 layout
->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_URL_LABEL
));
195 layout
->AddView(url_tf_
);
196 layout
->AddView(url_iv_
);
198 // On RTL UIs (such as Arabic and Hebrew) the description text is not
199 // displayed correctly since it contains the substring "%s". This substring
200 // is not interpreted by the Unicode BiDi algorithm as an LTR string and
201 // therefore the end result is that the following right to left text is
202 // displayed: ".three two s% one" (where 'one', 'two', etc. are words in
205 // In order to fix this problem we transform the substring "%s" so that it
206 // is displayed correctly when rendered in an RTL context.
207 layout
->StartRowWithPadding(0, 2, 0, unrelated_y
);
208 base::string16 description
= l10n_util::GetStringUTF16(
209 IDS_SEARCH_ENGINES_EDITOR_URL_DESCRIPTION_LABEL
);
210 if (base::i18n::IsRTL()) {
211 const base::string16
reversed_percent(base::ASCIIToUTF16("s%"));
212 base::string16::size_type percent_index
=
213 description
.find(base::ASCIIToUTF16("%s"),
214 static_cast<base::string16::size_type
>(0));
215 if (percent_index
!= base::string16::npos
)
216 description
.replace(percent_index
,
217 reversed_percent
.length(),
221 views::Label
* description_label
= new views::Label(description
);
222 description_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
223 layout
->AddView(description_label
);
225 layout
->AddPaddingRow(0, related_y
);
228 views::Label
* EditSearchEngineDialog::CreateLabel(int message_id
) {
229 views::Label
* label
=
230 new views::Label(l10n_util::GetStringUTF16(message_id
));
231 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
235 Textfield
* EditSearchEngineDialog::CreateTextfield(const base::string16
& text
) {
236 Textfield
* text_field
= new Textfield();
237 text_field
->SetText(text
);
238 text_field
->set_controller(this);
242 void EditSearchEngineDialog::UpdateImageViews() {
243 UpdateImageView(keyword_iv_
, controller_
->IsKeywordValid(keyword_tf_
->text()),
244 IDS_SEARCH_ENGINES_INVALID_KEYWORD_TT
);
245 UpdateImageView(url_iv_
,
246 controller_
->IsURLValid(base::UTF16ToUTF8(url_tf_
->text())),
247 IDS_SEARCH_ENGINES_INVALID_URL_TT
);
248 UpdateImageView(title_iv_
, controller_
->IsTitleValid(title_tf_
->text()),
249 IDS_SEARCH_ENGINES_INVALID_TITLE_TT
);
252 void EditSearchEngineDialog::UpdateImageView(views::ImageView
* image_view
,
254 int invalid_message_id
) {
255 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
257 image_view
->SetTooltipText(base::string16());
258 image_view
->SetImage(rb
.GetImageSkiaNamed(IDR_INPUT_GOOD
));
260 image_view
->SetTooltipText(l10n_util::GetStringUTF16(invalid_message_id
));
261 image_view
->SetImage(rb
.GetImageSkiaNamed(IDR_INPUT_ALERT
));