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

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

Powered by Google App Engine
This is Rietveld 408576698