Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(44)

Side by Side Diff: chrome/browser/ui/views/payments/editor_view_controller.cc

Issue 2881643002: Focus first invalid field of payment request editor (Closed)
Patch Set: Last final nit :-) Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/views/payments/editor_view_controller.h" 5 #include "chrome/browser/ui/views/payments/editor_view_controller.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <map> 8 #include <map>
9 #include <memory> 9 #include <memory>
10 #include <utility> 10 #include <utility>
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
72 } 72 }
73 73
74 } // namespace 74 } // namespace
75 75
76 EditorViewController::EditorViewController( 76 EditorViewController::EditorViewController(
77 PaymentRequestSpec* spec, 77 PaymentRequestSpec* spec,
78 PaymentRequestState* state, 78 PaymentRequestState* state,
79 PaymentRequestDialogView* dialog, 79 PaymentRequestDialogView* dialog,
80 BackNavigationType back_navigation_type) 80 BackNavigationType back_navigation_type)
81 : PaymentRequestSheetController(spec, state, dialog), 81 : PaymentRequestSheetController(spec, state, dialog),
82 first_field_view_(nullptr), 82 initial_focus_field_view_(nullptr),
83 back_navigation_type_(back_navigation_type) {} 83 back_navigation_type_(back_navigation_type) {}
84 84
85 EditorViewController::~EditorViewController() {} 85 EditorViewController::~EditorViewController() {}
86 86
87 void EditorViewController::DisplayErrorMessageForField( 87 void EditorViewController::DisplayErrorMessageForField(
88 const EditorField& field, 88 const EditorField& field,
89 const base::string16& error_message) { 89 const base::string16& error_message) {
90 const auto& label_view_it = error_labels_.find(field); 90 const auto& label_view_it = error_labels_.find(field);
91 DCHECK(label_view_it != error_labels_.end()); 91 DCHECK(label_view_it != error_labels_.end());
92 92
93 label_view_it->second->RemoveAllChildViews(/*delete_children=*/true); 93 label_view_it->second->RemoveAllChildViews(/*delete_children=*/true);
94 if (!error_message.empty()) { 94 if (!error_message.empty()) {
95 label_view_it->second->AddChildView( 95 label_view_it->second->AddChildView(
96 CreateErrorLabelView(error_message, field).release()); 96 CreateErrorLabelView(error_message, field).release());
97 } 97 }
98 RelayoutPane(); 98 RelayoutPane();
99 } 99 }
100 100
101 std::unique_ptr<views::View> EditorViewController::CreateHeaderView() { 101 std::unique_ptr<views::View> EditorViewController::CreateHeaderView() {
102 return nullptr; 102 return nullptr;
103 } 103 }
104 104
105 std::unique_ptr<views::View> EditorViewController::CreateCustomFieldView( 105 std::unique_ptr<views::View> EditorViewController::CreateCustomFieldView(
106 autofill::ServerFieldType type) { 106 autofill::ServerFieldType type,
107 views::View** focusable_field) {
107 return nullptr; 108 return nullptr;
108 } 109 }
109 110
110 std::unique_ptr<views::View> EditorViewController::CreateExtraViewForField( 111 std::unique_ptr<views::View> EditorViewController::CreateExtraViewForField(
111 autofill::ServerFieldType type) { 112 autofill::ServerFieldType type) {
112 return nullptr; 113 return nullptr;
113 } 114 }
114 115
115 std::unique_ptr<views::Button> EditorViewController::CreatePrimaryButton() { 116 std::unique_ptr<views::Button> EditorViewController::CreatePrimaryButton() {
116 std::unique_ptr<views::Button> button( 117 std::unique_ptr<views::Button> button(
(...skipping 17 matching lines...) Expand all
134 std::unique_ptr<views::View> header_view = CreateHeaderView(); 135 std::unique_ptr<views::View> header_view = CreateHeaderView();
135 if (header_view.get()) 136 if (header_view.get())
136 content_view->AddChildView(header_view.release()); 137 content_view->AddChildView(header_view.release());
137 138
138 // The heart of the editor dialog: all the input fields with their labels. 139 // The heart of the editor dialog: all the input fields with their labels.
139 content_view->AddChildView(CreateEditorView().release()); 140 content_view->AddChildView(CreateEditorView().release());
140 } 141 }
141 142
142 void EditorViewController::UpdateEditorView() { 143 void EditorViewController::UpdateEditorView() {
143 UpdateContentView(); 144 UpdateContentView();
144 // TODO(crbug.com/704254): Find how to update the parent view bounds so that 145 UpdateFocus(GetFirstFocusedView());
145 // the vertical scrollbar size gets updated.
146 dialog()->EditorViewUpdated(); 146 dialog()->EditorViewUpdated();
147 } 147 }
148 148
149 void EditorViewController::ButtonPressed(views::Button* sender, 149 void EditorViewController::ButtonPressed(views::Button* sender,
150 const ui::Event& event) { 150 const ui::Event& event) {
151 switch (sender->tag()) { 151 switch (sender->tag()) {
152 case static_cast<int>(EditorViewControllerTags::SAVE_BUTTON): 152 case static_cast<int>(EditorViewControllerTags::SAVE_BUTTON):
153 if (ValidateModelAndSave()) { 153 if (ValidateModelAndSave()) {
154 switch (back_navigation_type_) { 154 switch (back_navigation_type_) {
155 case BackNavigationType::kOneStep: 155 case BackNavigationType::kOneStep:
156 dialog()->GoBack(); 156 dialog()->GoBack();
157 break; 157 break;
158 case BackNavigationType::kPaymentSheet: 158 case BackNavigationType::kPaymentSheet:
159 dialog()->GoBackToPaymentSheet(); 159 dialog()->GoBackToPaymentSheet();
160 break; 160 break;
161 } 161 }
162 } 162 }
163 break; 163 break;
164 default: 164 default:
165 PaymentRequestSheetController::ButtonPressed(sender, event); 165 PaymentRequestSheetController::ButtonPressed(sender, event);
166 break; 166 break;
167 } 167 }
168 } 168 }
169 169
170 views::View* EditorViewController::GetFirstFocusedView() { 170 views::View* EditorViewController::GetFirstFocusedView() {
171 if (first_field_view_) 171 if (initial_focus_field_view_)
172 return first_field_view_; 172 return initial_focus_field_view_;
173 return PaymentRequestSheetController::GetFirstFocusedView(); 173 return PaymentRequestSheetController::GetFirstFocusedView();
174 } 174 }
175 175
176 void EditorViewController::ContentsChanged(views::Textfield* sender, 176 void EditorViewController::ContentsChanged(views::Textfield* sender,
177 const base::string16& new_contents) { 177 const base::string16& new_contents) {
178 static_cast<ValidatingTextfield*>(sender)->OnContentsChanged(); 178 static_cast<ValidatingTextfield*>(sender)->OnContentsChanged();
179 } 179 }
180 180
181 void EditorViewController::OnPerformAction(views::Combobox* sender) { 181 void EditorViewController::OnPerformAction(views::Combobox* sender) {
182 static_cast<ValidatingCombobox*>(sender)->OnContentsChanged(); 182 static_cast<ValidatingCombobox*>(sender)->OnContentsChanged();
183 } 183 }
184 184
185 std::unique_ptr<views::View> EditorViewController::CreateEditorView() { 185 std::unique_ptr<views::View> EditorViewController::CreateEditorView() {
186 std::unique_ptr<views::View> editor_view = base::MakeUnique<views::View>(); 186 std::unique_ptr<views::View> editor_view = base::MakeUnique<views::View>();
187 text_fields_.clear(); 187 text_fields_.clear();
188 comboboxes_.clear(); 188 comboboxes_.clear();
189 initial_focus_field_view_ = nullptr;
189 190
190 // The editor view is padded horizontally. 191 // The editor view is padded horizontally.
191 editor_view->SetBorder(views::CreateEmptyBorder( 192 editor_view->SetBorder(views::CreateEmptyBorder(
192 0, payments::kPaymentRequestRowHorizontalInsets, 0, 193 0, payments::kPaymentRequestRowHorizontalInsets, 0,
193 payments::kPaymentRequestRowHorizontalInsets)); 194 payments::kPaymentRequestRowHorizontalInsets));
194 195
195 // All views have fixed size except the Field which stretches. The fixed 196 // All views have fixed size except the Field which stretches. The fixed
196 // padding at the end is computed so that Field views have a minimum of 197 // padding at the end is computed so that Field views have a minimum of
197 // 176/272dp (short/long fields) as per spec. 198 // 176/272dp (short/long fields) as per spec.
198 // ___________________________________________________________________________ 199 // ___________________________________________________________________________
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
251 0, views::GridLayout::FIXED, long_extra_view_width, 252 0, views::GridLayout::FIXED, long_extra_view_width,
252 0); 253 0);
253 // The padding at the end is fixed, computed to make sure the long field 254 // The padding at the end is fixed, computed to make sure the long field
254 // maintains its minimum width. 255 // maintains its minimum width.
255 int long_padding = kDialogMinWidth - kLongFieldMinimumWidth - kLabelWidth - 256 int long_padding = kDialogMinWidth - kLongFieldMinimumWidth - kLabelWidth -
256 (2 * kPaymentRequestRowHorizontalInsets) - 257 (2 * kPaymentRequestRowHorizontalInsets) -
257 kLabelInputFieldHorizontalPadding - 258 kLabelInputFieldHorizontalPadding -
258 kFieldExtraViewHorizontalPadding - long_extra_view_width; 259 kFieldExtraViewHorizontalPadding - long_extra_view_width;
259 columns_long->AddPaddingColumn(0, long_padding); 260 columns_long->AddPaddingColumn(0, long_padding);
260 261
261 for (const auto& field : GetFieldDefinitions()) 262 views::View* first_field = nullptr;
262 CreateInputField(editor_layout.get(), field); 263 for (const auto& field : GetFieldDefinitions()) {
264 bool valid = false;
265 views::View* focusable_field =
266 CreateInputField(editor_layout.get(), field, &valid);
267 if (!first_field)
268 first_field = focusable_field;
269 if (!initial_focus_field_view_ && !valid)
270 initial_focus_field_view_ = focusable_field;
271 }
272
273 if (!initial_focus_field_view_)
274 initial_focus_field_view_ = first_field;
263 275
264 // Adds the "* indicates a required field" label in "disabled" grey text. 276 // Adds the "* indicates a required field" label in "disabled" grey text.
265 std::unique_ptr<views::Label> required_field = base::MakeUnique<views::Label>( 277 std::unique_ptr<views::Label> required_field = base::MakeUnique<views::Label>(
266 l10n_util::GetStringUTF16(IDS_PAYMENTS_REQUIRED_FIELD_MESSAGE)); 278 l10n_util::GetStringUTF16(IDS_PAYMENTS_REQUIRED_FIELD_MESSAGE));
267 required_field->SetDisabledColor( 279 required_field->SetDisabledColor(
268 required_field->GetNativeTheme()->GetSystemColor( 280 required_field->GetNativeTheme()->GetSystemColor(
269 ui::NativeTheme::kColorId_LabelDisabledColor)); 281 ui::NativeTheme::kColorId_LabelDisabledColor));
270 required_field->SetEnabled(false); 282 required_field->SetEnabled(false);
271 283
272 views::ColumnSet* required_field_columns = editor_layout->AddColumnSet(2); 284 views::ColumnSet* required_field_columns = editor_layout->AddColumnSet(2);
273 required_field_columns->AddColumn(views::GridLayout::LEADING, 285 required_field_columns->AddColumn(views::GridLayout::LEADING,
274 views::GridLayout::CENTER, 1, 286 views::GridLayout::CENTER, 1,
275 views::GridLayout::USE_PREF, 0, 0); 287 views::GridLayout::USE_PREF, 0, 0);
276 editor_layout->StartRow(0, 2); 288 editor_layout->StartRow(0, 2);
277 editor_layout->AddView(required_field.release()); 289 editor_layout->AddView(required_field.release());
278 290
279 editor_view->SetLayoutManager(editor_layout.release()); 291 editor_view->SetLayoutManager(editor_layout.release());
280 292
281 return editor_view; 293 return editor_view;
282 } 294 }
283 295
284 // Each input field is a 4-quadrant grid. 296 // Each input field is a 4-quadrant grid.
285 // +----------------------------------------------------------+ 297 // +----------------------------------------------------------+
286 // | Field Label | Input field (textfield/combobox) | 298 // | Field Label | Input field (textfield/combobox) |
287 // |_______________________|__________________________________| 299 // |_______________________|__________________________________|
288 // | (empty) | Error label | 300 // | (empty) | Error label |
289 // +----------------------------------------------------------+ 301 // +----------------------------------------------------------+
290 void EditorViewController::CreateInputField(views::GridLayout* layout, 302 views::View* EditorViewController::CreateInputField(views::GridLayout* layout,
291 const EditorField& field) { 303 const EditorField& field,
304 bool* valid) {
292 int column_set = 305 int column_set =
293 field.length_hint == EditorField::LengthHint::HINT_SHORT ? 0 : 1; 306 field.length_hint == EditorField::LengthHint::HINT_SHORT ? 0 : 1;
294 307
295 // This is the top padding for every row. 308 // This is the top padding for every row.
296 constexpr int kInputRowSpacing = 6; 309 constexpr int kInputRowSpacing = 6;
297 layout->StartRowWithPadding(0, column_set, 0, kInputRowSpacing); 310 layout->StartRowWithPadding(0, column_set, 0, kInputRowSpacing);
298 311
299 std::unique_ptr<views::Label> label = base::MakeUnique<views::Label>( 312 std::unique_ptr<views::Label> label = base::MakeUnique<views::Label>(
300 field.required ? field.label + base::ASCIIToUTF16("*") : field.label); 313 field.required ? field.label + base::ASCIIToUTF16("*") : field.label);
301 314
302 label->SetMultiLine(true); 315 label->SetMultiLine(true);
303 layout->AddView(label.release()); 316 layout->AddView(label.release());
304 317
318 views::View* focusable_field = nullptr;
319
305 constexpr int kInputFieldHeight = 28; 320 constexpr int kInputFieldHeight = 28;
306 if (field.control_type == EditorField::ControlType::TEXTFIELD) { 321 if (field.control_type == EditorField::ControlType::TEXTFIELD) {
307 ValidatingTextfield* text_field = 322 ValidatingTextfield* text_field =
308 new ValidatingTextfield(CreateValidationDelegate(field)); 323 new ValidatingTextfield(CreateValidationDelegate(field));
309 text_field->SetText(GetInitialValueForType(field.type)); 324 text_field->SetText(GetInitialValueForType(field.type));
310 text_field->set_controller(this); 325 text_field->set_controller(this);
311 // Using autofill field type as a view ID (for testing). 326 // Using autofill field type as a view ID (for testing).
312 text_field->set_id(static_cast<int>(field.type)); 327 text_field->set_id(static_cast<int>(field.type));
313 text_fields_.insert(std::make_pair(text_field, field)); 328 text_fields_.insert(std::make_pair(text_field, field));
314 329 focusable_field = text_field;
315 // TODO(crbug.com/718582): Make the initial focus the first incomplete/empty 330 *valid = text_field->IsValid();
316 // field.
317 if (!first_field_view_)
318 first_field_view_ = text_field;
319 331
320 // |text_field| will now be owned by |row|. 332 // |text_field| will now be owned by |row|.
321 layout->AddView(text_field, 1, 1, views::GridLayout::FILL, 333 layout->AddView(text_field, 1, 1, views::GridLayout::FILL,
322 views::GridLayout::FILL, 0, kInputFieldHeight); 334 views::GridLayout::FILL, 0, kInputFieldHeight);
323 } else if (field.control_type == EditorField::ControlType::COMBOBOX) { 335 } else if (field.control_type == EditorField::ControlType::COMBOBOX) {
324 ValidatingCombobox* combobox = new ValidatingCombobox( 336 ValidatingCombobox* combobox = new ValidatingCombobox(
325 GetComboboxModelForType(field.type), CreateValidationDelegate(field)); 337 GetComboboxModelForType(field.type), CreateValidationDelegate(field));
326 base::string16 initial_value = GetInitialValueForType(field.type); 338 base::string16 initial_value = GetInitialValueForType(field.type);
327 if (!initial_value.empty()) 339 if (!initial_value.empty())
328 combobox->SelectValue(initial_value); 340 combobox->SelectValue(initial_value);
329 // Using autofill field type as a view ID. 341 // Using autofill field type as a view ID.
330 combobox->set_id(static_cast<int>(field.type)); 342 combobox->set_id(static_cast<int>(field.type));
331 combobox->set_listener(this); 343 combobox->set_listener(this);
332 comboboxes_.insert(std::make_pair(combobox, field)); 344 comboboxes_.insert(std::make_pair(combobox, field));
333 345 focusable_field = combobox;
334 if (!first_field_view_) 346 *valid = combobox->IsValid();
335 first_field_view_ = combobox;
336 347
337 // |combobox| will now be owned by |row|. 348 // |combobox| will now be owned by |row|.
338 layout->AddView(combobox, 1, 1, views::GridLayout::FILL, 349 layout->AddView(combobox, 1, 1, views::GridLayout::FILL,
339 views::GridLayout::FILL, 0, kInputFieldHeight); 350 views::GridLayout::FILL, 0, kInputFieldHeight);
340 } else { 351 } else {
341 // Custom field view will now be owned by |row|. And it must be valid since 352 // Custom field view will now be owned by |row|. And it must be valid since
342 // the derived class specified a custom view for this field. 353 // the derived class specified a custom view for this field.
343 std::unique_ptr<views::View> field_view = CreateCustomFieldView(field.type); 354 DCHECK(!focusable_field);
355 std::unique_ptr<views::View> field_view =
356 CreateCustomFieldView(field.type, &focusable_field);
344 DCHECK(field_view); 357 DCHECK(field_view);
345 layout->AddView(field_view.release()); 358 layout->AddView(field_view.release());
346 } 359 }
347 360
348 // If an extra view needs to go alongside the input field view, add it to the 361 // If an extra view needs to go alongside the input field view, add it to the
349 // last column. 362 // last column.
350 std::unique_ptr<views::View> extra_view = CreateExtraViewForField(field.type); 363 std::unique_ptr<views::View> extra_view = CreateExtraViewForField(field.type);
351 if (extra_view) 364 if (extra_view)
352 layout->AddView(extra_view.release()); 365 layout->AddView(extra_view.release());
353 366
354 layout->StartRow(0, column_set); 367 layout->StartRow(0, column_set);
355 layout->SkipColumns(1); 368 layout->SkipColumns(1);
356 std::unique_ptr<views::View> error_label_view = 369 std::unique_ptr<views::View> error_label_view =
357 base::MakeUnique<views::View>(); 370 base::MakeUnique<views::View>();
358 error_label_view->SetLayoutManager(new views::FillLayout); 371 error_label_view->SetLayoutManager(new views::FillLayout);
359 error_labels_[field] = error_label_view.get(); 372 error_labels_[field] = error_label_view.get();
360 layout->AddView(error_label_view.release()); 373 layout->AddView(error_label_view.release());
361 374
362 // Bottom padding for the row. 375 // Bottom padding for the row.
363 layout->AddPaddingRow(0, kInputRowSpacing); 376 layout->AddPaddingRow(0, kInputRowSpacing);
377 return focusable_field;
364 } 378 }
365 379
366 int EditorViewController::ComputeWidestExtraViewWidth( 380 int EditorViewController::ComputeWidestExtraViewWidth(
367 EditorField::LengthHint size) { 381 EditorField::LengthHint size) {
368 int widest_column_width = 0; 382 int widest_column_width = 0;
369 383
370 for (const auto& field : GetFieldDefinitions()) { 384 for (const auto& field : GetFieldDefinitions()) {
371 if (field.length_hint != size) 385 if (field.length_hint != size)
372 continue; 386 continue;
373 387
374 std::unique_ptr<views::View> extra_view = 388 std::unique_ptr<views::View> extra_view =
375 CreateExtraViewForField(field.type); 389 CreateExtraViewForField(field.type);
376 if (!extra_view) 390 if (!extra_view)
377 continue; 391 continue;
378 widest_column_width = 392 widest_column_width =
379 std::max(extra_view->GetPreferredSize().width(), widest_column_width); 393 std::max(extra_view->GetPreferredSize().width(), widest_column_width);
380 } 394 }
381 return widest_column_width; 395 return widest_column_width;
382 } 396 }
383 397
384 } // namespace payments 398 } // namespace payments
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698