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/template_url.h"
12 #include "chrome/browser/ui/search_engines/edit_search_engine_controller.h"
13 #include "chrome/browser/ui/views/constrained_window_views.h"
14 #include "grit/generated_resources.h"
15 #include "grit/theme_resources.h"
16 #include "grit/ui_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 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 // We don't allow users to edit prepopulate URLs. This is done as
132 // occasionally we need to update the URL of prepopulated TemplateURLs.
133 url_tf_
->SetReadOnly(controller_
->template_url()->prepopulate_id() != 0);
135 title_tf_
= CreateTextfield(base::string16());
136 keyword_tf_
= CreateTextfield(base::string16());
137 url_tf_
= CreateTextfield(base::string16());
139 title_iv_
= new views::ImageView();
140 keyword_iv_
= new views::ImageView();
141 url_iv_
= new views::ImageView();
145 const int related_x
= views::kRelatedControlHorizontalSpacing
;
146 const int related_y
= views::kRelatedControlVerticalSpacing
;
147 const int unrelated_y
= views::kUnrelatedControlVerticalSpacing
;
149 // View and GridLayout take care of deleting GridLayout for us.
150 GridLayout
* layout
= GridLayout::CreatePanel(this);
151 SetLayoutManager(layout
);
153 // Define the structure of the layout.
156 views::ColumnSet
* column_set
= layout
->AddColumnSet(0);
157 column_set
->AddPaddingColumn(1, 0);
158 column_set
->AddColumn(GridLayout::FILL
, GridLayout::CENTER
, 0,
159 GridLayout::USE_PREF
, 0, 0);
160 column_set
->AddPaddingColumn(0, related_x
);
161 column_set
->AddColumn(GridLayout::FILL
, GridLayout::CENTER
, 0,
162 GridLayout::USE_PREF
, 0, 0);
163 column_set
->LinkColumnSizes(1, 3, -1);
165 // For the Textfields.
166 column_set
= layout
->AddColumnSet(1);
167 column_set
->AddColumn(GridLayout::LEADING
, GridLayout::CENTER
, 0,
168 GridLayout::USE_PREF
, 0, 0);
169 column_set
->AddPaddingColumn(0, related_x
);
170 column_set
->AddColumn(GridLayout::FILL
, GridLayout::CENTER
, 1,
171 GridLayout::USE_PREF
, 0, 0);
172 column_set
->AddPaddingColumn(0, related_x
);
173 column_set
->AddColumn(GridLayout::CENTER
, GridLayout::CENTER
, 0,
174 GridLayout::USE_PREF
, 0, 0);
176 // For the description.
177 column_set
= layout
->AddColumnSet(2);
178 column_set
->AddColumn(GridLayout::FILL
, GridLayout::CENTER
, 0,
179 GridLayout::USE_PREF
, 0, 0);
182 layout
->StartRow(0, 1);
183 layout
->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_LABEL
));
184 layout
->AddView(title_tf_
);
185 layout
->AddView(title_iv_
);
187 layout
->StartRowWithPadding(0, 1, 0, related_y
);
188 layout
->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_LABEL
));
189 layout
->AddView(keyword_tf_
);
190 layout
->AddView(keyword_iv_
);
192 layout
->StartRowWithPadding(0, 1, 0, related_y
);
193 layout
->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_URL_LABEL
));
194 layout
->AddView(url_tf_
);
195 layout
->AddView(url_iv_
);
197 // On RTL UIs (such as Arabic and Hebrew) the description text is not
198 // displayed correctly since it contains the substring "%s". This substring
199 // is not interpreted by the Unicode BiDi algorithm as an LTR string and
200 // therefore the end result is that the following right to left text is
201 // displayed: ".three two s% one" (where 'one', 'two', etc. are words in
204 // In order to fix this problem we transform the substring "%s" so that it
205 // is displayed correctly when rendered in an RTL context.
206 layout
->StartRowWithPadding(0, 2, 0, unrelated_y
);
207 base::string16 description
= l10n_util::GetStringUTF16(
208 IDS_SEARCH_ENGINES_EDITOR_URL_DESCRIPTION_LABEL
);
209 if (base::i18n::IsRTL()) {
210 const base::string16
reversed_percent(base::ASCIIToUTF16("s%"));
211 base::string16::size_type percent_index
=
212 description
.find(base::ASCIIToUTF16("%s"),
213 static_cast<base::string16::size_type
>(0));
214 if (percent_index
!= base::string16::npos
)
215 description
.replace(percent_index
,
216 reversed_percent
.length(),
220 views::Label
* description_label
= new views::Label(description
);
221 description_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
222 layout
->AddView(description_label
);
224 layout
->AddPaddingRow(0, related_y
);
227 views::Label
* EditSearchEngineDialog::CreateLabel(int message_id
) {
228 views::Label
* label
=
229 new views::Label(l10n_util::GetStringUTF16(message_id
));
230 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
234 Textfield
* EditSearchEngineDialog::CreateTextfield(const base::string16
& text
) {
235 Textfield
* text_field
= new Textfield();
236 text_field
->SetText(text
);
237 text_field
->set_controller(this);
241 void EditSearchEngineDialog::UpdateImageViews() {
242 UpdateImageView(keyword_iv_
, controller_
->IsKeywordValid(keyword_tf_
->text()),
243 IDS_SEARCH_ENGINES_INVALID_KEYWORD_TT
);
244 UpdateImageView(url_iv_
,
245 controller_
->IsURLValid(base::UTF16ToUTF8(url_tf_
->text())),
246 IDS_SEARCH_ENGINES_INVALID_URL_TT
);
247 UpdateImageView(title_iv_
, controller_
->IsTitleValid(title_tf_
->text()),
248 IDS_SEARCH_ENGINES_INVALID_TITLE_TT
);
251 void EditSearchEngineDialog::UpdateImageView(views::ImageView
* image_view
,
253 int invalid_message_id
) {
254 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
256 image_view
->SetTooltipText(base::string16());
257 image_view
->SetImage(rb
.GetImageSkiaNamed(IDR_INPUT_GOOD
));
259 image_view
->SetTooltipText(l10n_util::GetStringUTF16(invalid_message_id
));
260 image_view
->SetImage(rb
.GetImageSkiaNamed(IDR_INPUT_ALERT
));