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

Side by Side Diff: third_party/WebKit/Source/core/editing/Editor.cpp

Issue 2151353002: [InputEvent] Replace |EditAction| with |InputType| (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@inputevent-text-styling
Patch Set: Replaced |EditAction| with |InputType| Created 4 years, 5 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 /* 1 /*
2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved. 2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4 * 4 *
5 * Redistribution and use in source and binary forms, with or without 5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions 6 * modification, are permitted provided that the following conditions
7 * are met: 7 * are met:
8 * 1. Redistributions of source code must retain the above copyright 8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer. 9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright 10 * 2. Redistributions in binary form must reproduce the above copyright
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
111 } 111 }
112 112
113 void dispatchInputEventEditableContentChanged(Element* startRoot, Element* endRo ot, InputEvent::InputType inputType, const String& data, InputEvent::EventIsComp osing isComposing) 113 void dispatchInputEventEditableContentChanged(Element* startRoot, Element* endRo ot, InputEvent::InputType inputType, const String& data, InputEvent::EventIsComp osing isComposing)
114 { 114 {
115 if (startRoot) 115 if (startRoot)
116 dispatchInputEvent(startRoot, inputType, data, isComposing); 116 dispatchInputEvent(startRoot, inputType, data, isComposing);
117 if (endRoot && endRoot != startRoot) 117 if (endRoot && endRoot != startRoot)
118 dispatchInputEvent(endRoot, inputType, data, isComposing); 118 dispatchInputEvent(endRoot, inputType, data, isComposing);
119 } 119 }
120 120
121 InputEvent::InputType inputTypeFromCommand(const CompositeEditCommand* command)
122 {
123 if (command->isTypingCommand()) {
124 const TypingCommand* typingCommand = toTypingCommand(command);
125 // TODO(chongz): Separate command types into more detailed InputType.
126 switch (typingCommand->commandTypeOfOpenCommand()) {
127 case TypingCommand::DeleteSelection:
128 case TypingCommand::DeleteKey:
129 case TypingCommand::ForwardDeleteKey:
130 return InputEvent::InputType::DeleteContent;
131 case TypingCommand::InsertText:
132 case TypingCommand::InsertLineBreak:
133 case TypingCommand::InsertParagraphSeparator:
134 case TypingCommand::InsertParagraphSeparatorInQuotedContent:
135 return InputEvent::InputType::InsertText;
136 default:
137 return InputEvent::InputType::None;
138 }
139 }
140
141 switch (command->editingAction()) {
142 // TODO(chongz): Handle remaining edit actions.
143 case EditActionBold:
144 return InputEvent::InputType::Bold;
145 case EditActionItalics:
146 return InputEvent::InputType::Italic;
147 case EditActionUnderline:
148 return InputEvent::InputType::Underline;
149 case EditActionStrikeThrough:
150 return InputEvent::InputType::StrikeThrough;
151 case EditActionSuperscript:
152 return InputEvent::InputType::Superscript;
153 case EditActionSubscript:
154 return InputEvent::InputType::Subscript;
155 default:
156 return InputEvent::InputType::None;
157 }
158 }
159
160 InputEvent::EventIsComposing isComposingFromCommand(const CompositeEditCommand* command) 121 InputEvent::EventIsComposing isComposingFromCommand(const CompositeEditCommand* command)
161 { 122 {
162 if (command->isTypingCommand() && toTypingCommand(command)->compositionType( ) != TypingCommand::TextCompositionNone) 123 if (command->isTypingCommand() && toTypingCommand(command)->compositionType( ) != TypingCommand::TextCompositionNone)
163 return InputEvent::EventIsComposing::IsComposing; 124 return InputEvent::EventIsComposing::IsComposing;
164 return InputEvent::EventIsComposing::NotComposing; 125 return InputEvent::EventIsComposing::NotComposing;
165 } 126 }
166 127
167 } // anonymous namespace 128 } // anonymous namespace
168 129
169 Editor::RevealSelectionScope::RevealSelectionScope(Editor* editor) 130 Editor::RevealSelectionScope::RevealSelectionScope(Editor* editor)
(...skipping 403 matching lines...) Expand 10 before | Expand all | Expand 10 after
573 return; 534 return;
574 535
575 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::P reventNesting | ReplaceSelectionCommand::SanitizeFragment; 536 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::P reventNesting | ReplaceSelectionCommand::SanitizeFragment;
576 if (selectReplacement) 537 if (selectReplacement)
577 options |= ReplaceSelectionCommand::SelectReplacement; 538 options |= ReplaceSelectionCommand::SelectReplacement;
578 if (smartReplace) 539 if (smartReplace)
579 options |= ReplaceSelectionCommand::SmartReplace; 540 options |= ReplaceSelectionCommand::SmartReplace;
580 if (matchStyle) 541 if (matchStyle)
581 options |= ReplaceSelectionCommand::MatchStyle; 542 options |= ReplaceSelectionCommand::MatchStyle;
582 DCHECK(frame().document()); 543 DCHECK(frame().document());
583 ReplaceSelectionCommand::create(*frame().document(), fragment, options, Edit ActionPaste)->apply(); 544 ReplaceSelectionCommand::create(*frame().document(), fragment, options, Inpu tEvent::InputType::Paste)->apply();
584 revealSelectionAfterEditingOperation(); 545 revealSelectionAfterEditingOperation();
585 } 546 }
586 547
587 void Editor::replaceSelectionWithText(const String& text, bool selectReplacement , bool smartReplace) 548 void Editor::replaceSelectionWithText(const String& text, bool selectReplacement , bool smartReplace)
588 { 549 {
589 replaceSelectionWithFragment(createFragmentFromText(selectedRange(), text), selectReplacement, smartReplace, true); 550 replaceSelectionWithFragment(createFragmentFromText(selectedRange(), text), selectReplacement, smartReplace, true);
590 } 551 }
591 552
592 // TODO(xiaochengh): Merge it with |replaceSelectionWithFragment()|. 553 // TODO(xiaochengh): Merge it with |replaceSelectionWithFragment()|.
593 void Editor::replaceSelectionAfterDragging(DocumentFragment* fragment, bool smar tReplace, bool plainText) 554 void Editor::replaceSelectionAfterDragging(DocumentFragment* fragment, bool smar tReplace, bool plainText)
594 { 555 {
595 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::S electReplacement | ReplaceSelectionCommand::PreventNesting; 556 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::S electReplacement | ReplaceSelectionCommand::PreventNesting;
596 if (smartReplace) 557 if (smartReplace)
597 options |= ReplaceSelectionCommand::SmartReplace; 558 options |= ReplaceSelectionCommand::SmartReplace;
598 if (plainText) 559 if (plainText)
599 options |= ReplaceSelectionCommand::MatchStyle; 560 options |= ReplaceSelectionCommand::MatchStyle;
600 DCHECK(frame().document()); 561 DCHECK(frame().document());
601 ReplaceSelectionCommand::create(*frame().document(), fragment, options, Edit ActionDrag)->apply(); 562 ReplaceSelectionCommand::create(*frame().document(), fragment, options, Inpu tEvent::InputType::Drag)->apply();
602 } 563 }
603 564
604 void Editor::moveSelectionAfterDragging(DocumentFragment* fragment, const Positi on& pos, bool smartInsert, bool smartDelete) 565 void Editor::moveSelectionAfterDragging(DocumentFragment* fragment, const Positi on& pos, bool smartInsert, bool smartDelete)
605 { 566 {
606 MoveSelectionCommand::create(fragment, pos, smartInsert, smartDelete)->apply (); 567 MoveSelectionCommand::create(fragment, pos, smartInsert, smartDelete)->apply ();
607 } 568 }
608 569
609 EphemeralRange Editor::selectedRange() 570 EphemeralRange Editor::selectedRange()
610 { 571 {
611 return frame().selection().selection().toNormalizedEphemeralRange(); 572 return frame().selection().selection().toNormalizedEphemeralRange();
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
655 target = frame().document()->body(); 616 target = frame().document()->body();
656 617
657 return target; 618 return target;
658 } 619 }
659 620
660 Element* Editor::findEventTargetFromSelection() const 621 Element* Editor::findEventTargetFromSelection() const
661 { 622 {
662 return findEventTargetFrom(frame().selection().selection()); 623 return findEventTargetFrom(frame().selection().selection());
663 } 624 }
664 625
665 void Editor::applyStyle(StylePropertySet* style, EditAction editingAction) 626 void Editor::applyStyle(StylePropertySet* style, InputEvent::InputType inputType )
666 { 627 {
667 switch (frame().selection().getSelectionType()) { 628 switch (frame().selection().getSelectionType()) {
668 case NoSelection: 629 case NoSelection:
669 // do nothing 630 // do nothing
670 break; 631 break;
671 case CaretSelection: 632 case CaretSelection:
672 computeAndSetTypingStyle(style, editingAction); 633 computeAndSetTypingStyle(style, inputType);
673 break; 634 break;
674 case RangeSelection: 635 case RangeSelection:
675 if (style) { 636 if (style) {
676 DCHECK(frame().document()); 637 DCHECK(frame().document());
677 ApplyStyleCommand::create(*frame().document(), EditingStyle::create( style), editingAction)->apply(); 638 ApplyStyleCommand::create(*frame().document(), EditingStyle::create( style), inputType)->apply();
678 } 639 }
679 break; 640 break;
680 } 641 }
681 } 642 }
682 643
683 void Editor::applyParagraphStyle(StylePropertySet* style, EditAction editingActi on) 644 void Editor::applyParagraphStyle(StylePropertySet* style, InputEvent::InputType inputType)
684 { 645 {
685 if (frame().selection().isNone() || !style) 646 if (frame().selection().isNone() || !style)
686 return; 647 return;
687 DCHECK(frame().document()); 648 DCHECK(frame().document());
688 ApplyStyleCommand::create(*frame().document(), EditingStyle::create(style), editingAction, ApplyStyleCommand::ForceBlockProperties)->apply(); 649 ApplyStyleCommand::create(*frame().document(), EditingStyle::create(style), inputType, ApplyStyleCommand::ForceBlockProperties)->apply();
689 } 650 }
690 651
691 void Editor::applyStyleToSelection(StylePropertySet* style, EditAction editingAc tion) 652 void Editor::applyStyleToSelection(StylePropertySet* style, InputEvent::InputTyp e inputType)
692 { 653 {
693 if (!style || style->isEmpty() || !canEditRichly()) 654 if (!style || style->isEmpty() || !canEditRichly())
694 return; 655 return;
695 656
696 applyStyle(style, editingAction); 657 applyStyle(style, inputType);
697 } 658 }
698 659
699 void Editor::applyParagraphStyleToSelection(StylePropertySet* style, EditAction editingAction) 660 void Editor::applyParagraphStyleToSelection(StylePropertySet* style, InputEvent: :InputType inputType)
700 { 661 {
701 if (!style || style->isEmpty() || !canEditRichly()) 662 if (!style || style->isEmpty() || !canEditRichly())
702 return; 663 return;
703 664
704 applyParagraphStyle(style, editingAction); 665 applyParagraphStyle(style, inputType);
705 } 666 }
706 667
707 bool Editor::selectionStartHasStyle(CSSPropertyID propertyID, const String& valu e) const 668 bool Editor::selectionStartHasStyle(CSSPropertyID propertyID, const String& valu e) const
708 { 669 {
709 EditingStyle* styleToCheck = EditingStyle::create(propertyID, value); 670 EditingStyle* styleToCheck = EditingStyle::create(propertyID, value);
710 EditingStyle* styleAtStart = EditingStyle::styleAtSelectionStart(frame().sel ection().selection(), 671 EditingStyle* styleAtStart = EditingStyle::styleAtSelectionStart(frame().sel ection().selection(),
711 propertyID == CSSPropertyBackgroundColor, styleToCheck->style()); 672 propertyID == CSSPropertyBackgroundColor, styleToCheck->style());
712 return styleToCheck->triStateOfStyle(styleAtStart); 673 return styleToCheck->triStateOfStyle(styleAtStart);
713 } 674 }
714 675
(...skipping 20 matching lines...) Expand all
735 startRoot->dispatchEvent(Event::create(EventTypeNames::webkitEditableCon tentChanged)); 696 startRoot->dispatchEvent(Event::create(EventTypeNames::webkitEditableCon tentChanged));
736 if (endRoot && endRoot != startRoot) 697 if (endRoot && endRoot != startRoot)
737 endRoot->dispatchEvent(Event::create(EventTypeNames::webkitEditableConte ntChanged)); 698 endRoot->dispatchEvent(Event::create(EventTypeNames::webkitEditableConte ntChanged));
738 } 699 }
739 700
740 void Editor::requestSpellcheckingAfterApplyingCommand(CompositeEditCommand* cmd) 701 void Editor::requestSpellcheckingAfterApplyingCommand(CompositeEditCommand* cmd)
741 { 702 {
742 // Note: Request spell checking for and only for |ReplaceSelectionCommand|s 703 // Note: Request spell checking for and only for |ReplaceSelectionCommand|s
743 // created in |Editor::replaceSelectionWithFragment()|. 704 // created in |Editor::replaceSelectionWithFragment()|.
744 // TODO(xiaochengh): May also need to do this after dragging crbug.com/29804 6. 705 // TODO(xiaochengh): May also need to do this after dragging crbug.com/29804 6.
745 if (cmd->editingAction() != EditActionPaste) 706 if (cmd->inputType() != InputEvent::InputType::Paste)
746 return; 707 return;
747 if (!spellChecker().isContinuousSpellCheckingEnabled()) 708 if (!spellChecker().isContinuousSpellCheckingEnabled())
748 return; 709 return;
749 if (!SpellChecker::isSpellCheckingEnabledFor(cmd->endingSelection())) 710 if (!SpellChecker::isSpellCheckingEnabledFor(cmd->endingSelection()))
750 return; 711 return;
751 DCHECK(cmd->isReplaceSelectionCommand()); 712 DCHECK(cmd->isReplaceSelectionCommand());
752 const EphemeralRange& insertedRange = toReplaceSelectionCommand(cmd)->insert edRange(); 713 const EphemeralRange& insertedRange = toReplaceSelectionCommand(cmd)->insert edRange();
753 if (insertedRange.isNull()) 714 if (insertedRange.isNull())
754 return; 715 return;
755 spellChecker().chunkAndMarkAllMisspellingsAndBadGrammar(cmd->endingSelection ().rootEditableElement(), insertedRange); 716 spellChecker().chunkAndMarkAllMisspellingsAndBadGrammar(cmd->endingSelection ().rootEditableElement(), insertedRange);
756 } 717 }
757 718
758 void Editor::appliedEditing(CompositeEditCommand* cmd) 719 void Editor::appliedEditing(CompositeEditCommand* cmd)
759 { 720 {
760 EventQueueScope scope; 721 EventQueueScope scope;
761 frame().document()->updateStyleAndLayout(); 722 frame().document()->updateStyleAndLayout();
762 723
763 // Request spell checking after pasting before any further DOM change. 724 // Request spell checking after pasting before any further DOM change.
764 requestSpellcheckingAfterApplyingCommand(cmd); 725 requestSpellcheckingAfterApplyingCommand(cmd);
765 726
766 EditCommandComposition* composition = cmd->composition(); 727 EditCommandComposition* composition = cmd->composition();
767 DCHECK(composition); 728 DCHECK(composition);
768 dispatchEditableContentChangedEvents(composition->startingRootEditableElemen t(), composition->endingRootEditableElement()); 729 dispatchEditableContentChangedEvents(composition->startingRootEditableElemen t(), composition->endingRootEditableElement());
769 // TODO(chongz): Filter empty InputType after spec is finalized. 730 // TODO(chongz): Filter empty InputType after spec is finalized.
770 dispatchInputEventEditableContentChanged(composition->startingRootEditableEl ement(), composition->endingRootEditableElement(), inputTypeFromCommand(cmd), cm d->textDataForInputEvent(), isComposingFromCommand(cmd)); 731 dispatchInputEventEditableContentChanged(composition->startingRootEditableEl ement(), composition->endingRootEditableElement(), cmd->inputType(), cmd->textDa taForInputEvent(), isComposingFromCommand(cmd));
771 VisibleSelection newSelection(cmd->endingSelection()); 732 VisibleSelection newSelection(cmd->endingSelection());
772 733
773 // Don't clear the typing style with this selection change. We do those thin gs elsewhere if necessary. 734 // Don't clear the typing style with this selection change. We do those thin gs elsewhere if necessary.
774 changeSelectionAfterCommand(newSelection, 0); 735 changeSelectionAfterCommand(newSelection, 0);
775 736
776 if (!cmd->preservesTypingStyle()) 737 if (!cmd->preservesTypingStyle())
777 frame().selection().clearTypingStyle(); 738 frame().selection().clearTypingStyle();
778 739
779 // Command will be equal to last edit command only in the case of typing 740 // Command will be equal to last edit command only in the case of typing
780 if (m_lastEditCommand.get() == cmd) { 741 if (m_lastEditCommand.get() == cmd) {
(...skipping 316 matching lines...) Expand 10 before | Expand all | Expand 10 after
1097 if (direction == NaturalWritingDirection) 1058 if (direction == NaturalWritingDirection)
1098 return; 1059 return;
1099 focusedElement->setAttribute(dirAttr, direction == LeftToRightWritingDir ection ? "ltr" : "rtl"); 1060 focusedElement->setAttribute(dirAttr, direction == LeftToRightWritingDir ection ? "ltr" : "rtl");
1100 focusedElement->dispatchInputEvent(); 1061 focusedElement->dispatchInputEvent();
1101 frame().document()->updateStyleAndLayoutTree(); 1062 frame().document()->updateStyleAndLayoutTree();
1102 return; 1063 return;
1103 } 1064 }
1104 1065
1105 MutableStylePropertySet* style = MutableStylePropertySet::create(HTMLQuirksM ode); 1066 MutableStylePropertySet* style = MutableStylePropertySet::create(HTMLQuirksM ode);
1106 style->setProperty(CSSPropertyDirection, direction == LeftToRightWritingDire ction ? "ltr" : direction == RightToLeftWritingDirection ? "rtl" : "inherit", fa lse); 1067 style->setProperty(CSSPropertyDirection, direction == LeftToRightWritingDire ction ? "ltr" : direction == RightToLeftWritingDirection ? "rtl" : "inherit", fa lse);
1107 applyParagraphStyleToSelection(style, EditActionSetWritingDirection); 1068 applyParagraphStyleToSelection(style, InputEvent::InputType::SetWritingDirec tion);
1108 } 1069 }
1109 1070
1110 void Editor::revealSelectionAfterEditingOperation(const ScrollAlignment& alignme nt, RevealExtentOption revealExtentOption) 1071 void Editor::revealSelectionAfterEditingOperation(const ScrollAlignment& alignme nt, RevealExtentOption revealExtentOption)
1111 { 1072 {
1112 if (m_preventRevealSelection) 1073 if (m_preventRevealSelection)
1113 return; 1074 return;
1114 1075
1115 frame().selection().revealSelection(alignment, revealExtentOption); 1076 frame().selection().revealSelection(alignment, revealExtentOption);
1116 } 1077 }
1117 1078
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
1210 startCaretRect.width() + extraWidthToEndOfLine, 1171 startCaretRect.width() + extraWidthToEndOfLine,
1211 startCaretRect.height()); 1172 startCaretRect.height());
1212 } 1173 }
1213 1174
1214 IntRect Editor::firstRectForRange(const Range* range) const 1175 IntRect Editor::firstRectForRange(const Range* range) const
1215 { 1176 {
1216 DCHECK(range); 1177 DCHECK(range);
1217 return firstRectForRange(EphemeralRange(range)); 1178 return firstRectForRange(EphemeralRange(range));
1218 } 1179 }
1219 1180
1220 void Editor::computeAndSetTypingStyle(StylePropertySet* style, EditAction editin gAction) 1181 void Editor::computeAndSetTypingStyle(StylePropertySet* style, InputEvent::Input Type inputType)
1221 { 1182 {
1222 if (!style || style->isEmpty()) { 1183 if (!style || style->isEmpty()) {
1223 frame().selection().clearTypingStyle(); 1184 frame().selection().clearTypingStyle();
1224 return; 1185 return;
1225 } 1186 }
1226 1187
1227 // Calculate the current typing style. 1188 // Calculate the current typing style.
1228 EditingStyle* typingStyle = nullptr; 1189 EditingStyle* typingStyle = nullptr;
1229 if (frame().selection().typingStyle()) { 1190 if (frame().selection().typingStyle()) {
1230 typingStyle = frame().selection().typingStyle()->copy(); 1191 typingStyle = frame().selection().typingStyle()->copy();
1231 typingStyle->overrideWithStyle(style); 1192 typingStyle->overrideWithStyle(style);
1232 } else { 1193 } else {
1233 typingStyle = EditingStyle::create(style); 1194 typingStyle = EditingStyle::create(style);
1234 } 1195 }
1235 1196
1236 typingStyle->prepareToApplyAt(frame().selection().selection().visibleStart() .deepEquivalent(), EditingStyle::PreserveWritingDirection); 1197 typingStyle->prepareToApplyAt(frame().selection().selection().visibleStart() .deepEquivalent(), EditingStyle::PreserveWritingDirection);
1237 1198
1238 // Handle block styles, substracting these from the typing style. 1199 // Handle block styles, substracting these from the typing style.
1239 EditingStyle* blockStyle = typingStyle->extractAndRemoveBlockProperties(); 1200 EditingStyle* blockStyle = typingStyle->extractAndRemoveBlockProperties();
1240 if (!blockStyle->isEmpty()) { 1201 if (!blockStyle->isEmpty()) {
1241 DCHECK(frame().document()); 1202 DCHECK(frame().document());
1242 ApplyStyleCommand::create(*frame().document(), blockStyle, editingAction )->apply(); 1203 ApplyStyleCommand::create(*frame().document(), blockStyle, inputType)->a pply();
1243 } 1204 }
1244 1205
1245 // Set the remaining style as the typing style. 1206 // Set the remaining style as the typing style.
1246 frame().selection().setTypingStyle(typingStyle); 1207 frame().selection().setTypingStyle(typingStyle);
1247 } 1208 }
1248 1209
1249 bool Editor::findString(const String& target, FindOptions options) 1210 bool Editor::findString(const String& target, FindOptions options)
1250 { 1211 {
1251 VisibleSelection selection = frame().selection().selection(); 1212 VisibleSelection selection = frame().selection().selection();
1252 1213
(...skipping 189 matching lines...) Expand 10 before | Expand all | Expand 10 after
1442 1403
1443 DEFINE_TRACE(Editor) 1404 DEFINE_TRACE(Editor)
1444 { 1405 {
1445 visitor->trace(m_frame); 1406 visitor->trace(m_frame);
1446 visitor->trace(m_lastEditCommand); 1407 visitor->trace(m_lastEditCommand);
1447 visitor->trace(m_undoStack); 1408 visitor->trace(m_undoStack);
1448 visitor->trace(m_mark); 1409 visitor->trace(m_mark);
1449 } 1410 }
1450 1411
1451 } // namespace blink 1412 } // namespace blink
OLDNEW
« no previous file with comments | « third_party/WebKit/Source/core/editing/Editor.h ('k') | third_party/WebKit/Source/core/editing/InputMethodController.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698