2 * Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 #include "htmlediting.h"
28 #include "css_computedstyle.h"
29 #include "css_value.h"
30 #include "css_valueimpl.h"
31 #include "cssproperties.h"
33 #include "dom_docimpl.h"
34 #include "dom_docimpl.h"
35 #include "dom_elementimpl.h"
36 #include "dom_nodeimpl.h"
37 #include "dom_position.h"
38 #include "dom_positioniterator.h"
39 #include "dom_stringimpl.h"
40 #include "dom_textimpl.h"
41 #include "dom2_rangeimpl.h"
42 #include "html_elementimpl.h"
43 #include "html_imageimpl.h"
44 #include "html_interchange.h"
45 #include "htmlattrs.h"
47 #include "khtml_part.h"
48 #include "khtml_part.h"
49 #include "khtmlview.h"
51 #include "render_object.h"
52 #include "render_style.h"
53 #include "render_text.h"
54 #include "visible_position.h"
55 #include "visible_units.h"
58 using DOM::CSSComputedStyleDeclarationImpl;
59 using DOM::CSSMutableStyleDeclarationImpl;
60 using DOM::CSSPrimitiveValue;
61 using DOM::CSSPrimitiveValueImpl;
62 using DOM::CSSProperty;
63 using DOM::CSSStyleDeclarationImpl;
65 using DOM::CSSValueImpl;
66 using DOM::DocumentFragmentImpl;
67 using DOM::DocumentImpl;
69 using DOM::DOMStringImpl;
70 using DOM::DoNotStayInBlock;
71 using DOM::DoNotUpdateLayout;
72 using DOM::EditingTextImpl;
73 using DOM::ElementImpl;
74 using DOM::EStayInBlock;
75 using DOM::HTMLElementImpl;
76 using DOM::HTMLImageElementImpl;
77 using DOM::NamedAttrMapImpl;
80 using DOM::NodeListImpl;
82 using DOM::PositionIterator;
85 using DOM::StayInBlock;
87 using DOM::TreeWalkerImpl;
90 #include "KWQAssertions.h"
91 #include "KWQLogging.h"
92 #include "KWQKHTMLPart.h"
96 #define ASSERT(assertion) ((void)0)
97 #define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
98 #define ASSERT_NOT_REACHED() ((void)0)
99 #define LOG(channel, formatAndArgs...) ((void)0)
100 #define ERROR(formatAndArgs...) ((void)0)
101 #define ASSERT(assertion) assert(assertion)
103 #define debugPosition(a,b) ((void)0)
104 #define debugNode(a,b) ((void)0)
108 #define IF_IMPL_NULL_RETURN_ARG(arg) do { \
109 if (isNull()) { return arg; } \
112 #define IF_IMPL_NULL_RETURN do { \
113 if (isNull()) { return; } \
118 static inline bool isNBSP(const QChar &c)
120 return c == QChar(0xa0);
123 static inline bool isWS(const QChar &c)
125 return c.isSpace() && c != QChar(0xa0);
128 static inline bool isWS(const DOMString &text)
130 if (text.length() != 1)
133 return isWS(text[0]);
136 static inline bool isWS(const Position &pos)
141 if (!pos.node()->isTextNode())
144 const DOMString &string = static_cast<TextImpl *>(pos.node())->data();
145 return isWS(string[pos.offset()]);
148 static const int spacesPerTab = 4;
150 static inline bool isTab(const DOMString &text)
152 static QChar tabCharacter = QChar(0x9);
153 if (text.length() != 1)
156 return text[0] == tabCharacter;
159 static inline bool isTableStructureNode(const NodeImpl *node)
161 RenderObject *r = node->renderer();
162 return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol()));
165 static DOMString &nonBreakingSpaceString()
167 static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
168 return nonBreakingSpaceString;
171 static DOMString &styleSpanClassString()
173 static DOMString styleSpanClassString = "khtml-style-span";
174 return styleSpanClassString;
177 static bool isEmptyStyleSpan(const NodeImpl *node)
179 if (!node || !node->isHTMLElement())
182 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
183 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
184 if (!inlineStyleDecl || inlineStyleDecl->length() == 0) {
185 NamedAttrMapImpl *map = elem->attributes();
186 if (map && map->length() == 1 && elem->getAttribute(ATTR_CLASS) == styleSpanClassString())
193 static DOMString &blockPlaceholderClassString()
195 static DOMString blockPlaceholderClassString = "khtml-block-placeholder";
196 return blockPlaceholderClassString;
199 static void derefNodesInList(QPtrList<NodeImpl> &list)
201 for (QPtrListIterator<NodeImpl> it(list); it.current(); ++it)
202 it.current()->deref();
205 static void debugPosition(const char *prefix, const Position &pos)
210 LOG(Editing, "%s <null>", prefix);
212 LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
215 static void debugNode(const char *prefix, const NodeImpl *node)
220 LOG(Editing, "%s <null>", prefix);
222 LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node);
225 //------------------------------------------------------------------------------------------
228 EditCommandPtr::EditCommandPtr()
232 EditCommandPtr::EditCommandPtr(EditCommand *impl) : SharedPtr<EditCommand>(impl)
236 EditCommandPtr::EditCommandPtr(const EditCommandPtr &o) : SharedPtr<EditCommand>(o)
240 EditCommandPtr::~EditCommandPtr()
244 EditCommandPtr &EditCommandPtr::operator=(const EditCommandPtr &c)
246 static_cast<SharedPtr<EditCommand> &>(*this) = c;
250 bool EditCommandPtr::isCompositeStep() const
252 IF_IMPL_NULL_RETURN_ARG(false);
253 return get()->isCompositeStep();
256 bool EditCommandPtr::isInsertTextCommand() const
258 IF_IMPL_NULL_RETURN_ARG(false);
259 return get()->isInsertTextCommand();
262 bool EditCommandPtr::isTypingCommand() const
264 IF_IMPL_NULL_RETURN_ARG(false);
265 return get()->isTypingCommand();
268 void EditCommandPtr::apply() const
274 void EditCommandPtr::unapply() const
280 void EditCommandPtr::reapply() const
286 EditAction EditCommandPtr::editingAction() const
288 IF_IMPL_NULL_RETURN_ARG(EditActionUnspecified);
289 return get()->editingAction();
292 DocumentImpl * const EditCommandPtr::document() const
294 IF_IMPL_NULL_RETURN_ARG(0);
295 return get()->document();
298 Selection EditCommandPtr::startingSelection() const
300 IF_IMPL_NULL_RETURN_ARG(Selection());
301 return get()->startingSelection();
304 Selection EditCommandPtr::endingSelection() const
306 IF_IMPL_NULL_RETURN_ARG(Selection());
307 return get()->endingSelection();
310 void EditCommandPtr::setStartingSelection(const Selection &s) const
313 get()->setStartingSelection(s);
316 void EditCommandPtr::setEndingSelection(const Selection &s) const
319 get()->setEndingSelection(s);
322 CSSMutableStyleDeclarationImpl *EditCommandPtr::typingStyle() const
324 IF_IMPL_NULL_RETURN_ARG(0);
325 return get()->typingStyle();
328 void EditCommandPtr::setTypingStyle(CSSMutableStyleDeclarationImpl *style) const
331 get()->setTypingStyle(style);
334 EditCommandPtr EditCommandPtr::parent() const
336 IF_IMPL_NULL_RETURN_ARG(0);
337 return get()->parent();
340 void EditCommandPtr::setParent(const EditCommandPtr &cmd) const
343 get()->setParent(cmd.get());
346 EditCommandPtr &EditCommandPtr::emptyCommand()
348 static EditCommandPtr m_emptyCommand;
349 return m_emptyCommand;
352 //------------------------------------------------------------------------------------------
355 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, ELegacyHTMLStyles usesLegacyStyles)
356 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
358 init(style, Position());
361 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position, ELegacyHTMLStyles usesLegacyStyles)
362 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
364 init(style, position);
367 void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
370 CSSMutableStyleDeclarationImpl *mutableStyle = style->makeMutable();
374 QString styleText("");
376 QValueListConstIterator<CSSProperty> end;
377 for (QValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
378 const CSSProperty *property = &*it;
380 // If position is empty or the position passed in already has the
381 // style, just move on.
382 if (position.isNotNull() && currentlyHasStyle(position, property))
385 // If needed, figure out if this change is a legacy HTML style change.
386 if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property))
390 styleText += property->cssText().string();
393 mutableStyle->deref();
395 // Save the result for later
396 m_cssStyle = styleText.stripWhiteSpace();
399 bool StyleChange::checkForLegacyHTMLStyleChange(const DOM::CSSProperty *property)
401 DOMString valueText(property->value()->cssText());
402 switch (property->id()) {
403 case CSS_PROP_FONT_WEIGHT:
404 if (strcasecmp(valueText, "bold") == 0) {
409 case CSS_PROP_FONT_STYLE:
410 if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) {
411 m_applyItalic = true;
419 bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
421 ASSERT(pos.isNotNull());
422 CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
425 CSSValueImpl *value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
430 bool result = strcasecmp(value->cssText(), property->value()->cssText()) == 0;
435 //------------------------------------------------------------------------------------------
438 EditCommand::EditCommand(DocumentImpl *document)
439 : m_document(document), m_state(NotApplied), m_typingStyle(0), m_parent(0)
442 ASSERT(m_document->part());
444 m_startingSelection = m_document->part()->selection();
445 m_endingSelection = m_startingSelection;
447 m_document->part()->setSelection(Selection(), false, true);
450 EditCommand::~EditCommand()
455 m_typingStyle->deref();
458 void EditCommand::apply()
461 ASSERT(m_document->part());
462 ASSERT(state() == NotApplied);
464 KHTMLPart *part = m_document->part();
466 ASSERT(part->selection().isNone());
472 // FIXME: Improve typing style.
473 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
474 if (!preservesTypingStyle())
477 if (!isCompositeStep()) {
478 document()->updateLayout();
479 EditCommandPtr cmd(this);
480 part->appliedEditing(cmd);
484 void EditCommand::unapply()
487 ASSERT(m_document->part());
488 ASSERT(state() == Applied);
490 bool topLevel = !isCompositeStep();
492 KHTMLPart *part = m_document->part();
495 part->setSelection(Selection(), false, true);
497 ASSERT(part->selection().isNone());
501 m_state = NotApplied;
504 document()->updateLayout();
505 EditCommandPtr cmd(this);
506 part->unappliedEditing(cmd);
510 void EditCommand::reapply()
513 ASSERT(m_document->part());
514 ASSERT(state() == NotApplied);
516 bool topLevel = !isCompositeStep();
518 KHTMLPart *part = m_document->part();
521 part->setSelection(Selection(), false, true);
523 ASSERT(part->selection().isNone());
530 document()->updateLayout();
531 EditCommandPtr cmd(this);
532 part->reappliedEditing(cmd);
536 void EditCommand::doReapply()
541 EditAction EditCommand::editingAction() const
543 return EditActionUnspecified;
546 void EditCommand::setStartingSelection(const Selection &s)
548 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
549 cmd->m_startingSelection = s;
552 void EditCommand::setEndingSelection(const Selection &s)
554 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
555 cmd->m_endingSelection = s;
558 void EditCommand::assignTypingStyle(CSSMutableStyleDeclarationImpl *style)
560 if (m_typingStyle == style)
563 CSSMutableStyleDeclarationImpl *old = m_typingStyle;
564 m_typingStyle = style;
566 m_typingStyle->ref();
571 void EditCommand::setTypingStyle(CSSMutableStyleDeclarationImpl *style)
573 // FIXME: Improve typing style.
574 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
575 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
576 cmd->assignTypingStyle(style);
579 bool EditCommand::preservesTypingStyle() const
584 bool EditCommand::isInsertTextCommand() const
589 bool EditCommand::isTypingCommand() const
594 //------------------------------------------------------------------------------------------
595 // CompositeEditCommand
597 CompositeEditCommand::CompositeEditCommand(DocumentImpl *document)
598 : EditCommand(document)
602 void CompositeEditCommand::doUnapply()
604 if (m_cmds.count() == 0) {
608 for (int i = m_cmds.count() - 1; i >= 0; --i)
609 m_cmds[i]->unapply();
611 setState(NotApplied);
614 void CompositeEditCommand::doReapply()
616 if (m_cmds.count() == 0) {
620 for (QValueList<EditCommandPtr>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
627 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
629 void CompositeEditCommand::applyCommandToComposite(EditCommandPtr &cmd)
631 cmd.setStartingSelection(endingSelection());
632 cmd.setEndingSelection(endingSelection());
638 void CompositeEditCommand::applyStyle(CSSStyleDeclarationImpl *style, EditAction editingAction)
640 EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction));
641 applyCommandToComposite(cmd);
644 void CompositeEditCommand::insertParagraphSeparator()
646 EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
647 applyCommandToComposite(cmd);
650 void CompositeEditCommand::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
652 EditCommandPtr cmd(new InsertNodeBeforeCommand(document(), insertChild, refChild));
653 applyCommandToComposite(cmd);
656 void CompositeEditCommand::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
658 if (refChild->parentNode()->lastChild() == refChild) {
659 appendNode(insertChild, refChild->parentNode());
662 ASSERT(refChild->nextSibling());
663 insertNodeBefore(insertChild, refChild->nextSibling());
667 void CompositeEditCommand::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
669 if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
670 NodeImpl *child = refChild->firstChild();
671 for (long i = 0; child && i < offset; i++)
672 child = child->nextSibling();
674 insertNodeBefore(insertChild, child);
676 appendNode(insertChild, refChild);
678 else if (refChild->caretMinOffset() >= offset) {
679 insertNodeBefore(insertChild, refChild);
681 else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
682 splitTextNode(static_cast<TextImpl *>(refChild), offset);
683 insertNodeBefore(insertChild, refChild);
686 insertNodeAfter(insertChild, refChild);
690 void CompositeEditCommand::appendNode(NodeImpl *appendChild, NodeImpl *parent)
692 EditCommandPtr cmd(new AppendNodeCommand(document(), appendChild, parent));
693 applyCommandToComposite(cmd);
696 void CompositeEditCommand::removeFullySelectedNode(NodeImpl *node)
698 if (isTableStructureNode(node)) {
699 // Do not remove an element of table structure; remove its contents.
700 NodeImpl *child = node->firstChild();
702 NodeImpl *remove = child;
703 child = child->nextSibling();
704 removeFullySelectedNode(remove);
708 EditCommandPtr cmd(new RemoveNodeCommand(document(), node));
709 applyCommandToComposite(cmd);
713 void CompositeEditCommand::removeNode(NodeImpl *removeChild)
715 EditCommandPtr cmd(new RemoveNodeCommand(document(), removeChild));
716 applyCommandToComposite(cmd);
719 void CompositeEditCommand::removeNodePreservingChildren(NodeImpl *removeChild)
721 EditCommandPtr cmd(new RemoveNodePreservingChildrenCommand(document(), removeChild));
722 applyCommandToComposite(cmd);
725 void CompositeEditCommand::splitTextNode(TextImpl *text, long offset)
727 EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, offset));
728 applyCommandToComposite(cmd);
731 void CompositeEditCommand::joinTextNodes(TextImpl *text1, TextImpl *text2)
733 EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2));
734 applyCommandToComposite(cmd);
737 void CompositeEditCommand::inputText(const DOMString &text, bool selectInsertedText)
739 InsertTextCommand *impl = new InsertTextCommand(document());
740 EditCommandPtr cmd(impl);
741 applyCommandToComposite(cmd);
742 impl->input(text, selectInsertedText);
745 void CompositeEditCommand::insertTextIntoNode(TextImpl *node, long offset, const DOMString &text)
747 EditCommandPtr cmd(new InsertIntoTextNode(document(), node, offset, text));
748 applyCommandToComposite(cmd);
751 void CompositeEditCommand::deleteTextFromNode(TextImpl *node, long offset, long count)
753 EditCommandPtr cmd(new DeleteFromTextNodeCommand(document(), node, offset, count));
754 applyCommandToComposite(cmd);
757 void CompositeEditCommand::replaceTextInNode(TextImpl *node, long offset, long count, const DOMString &replacementText)
759 EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count));
760 applyCommandToComposite(deleteCommand);
761 EditCommandPtr insertCommand(new InsertIntoTextNode(document(), node, offset, replacementText));
762 applyCommandToComposite(insertCommand);
765 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete)
767 if (endingSelection().isRange()) {
768 EditCommandPtr cmd(new DeleteSelectionCommand(document(), smartDelete, mergeBlocksAfterDelete));
769 applyCommandToComposite(cmd);
773 void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
775 if (selection.isRange()) {
776 EditCommandPtr cmd(new DeleteSelectionCommand(document(), selection, smartDelete, mergeBlocksAfterDelete));
777 applyCommandToComposite(cmd);
781 void CompositeEditCommand::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
783 EditCommandPtr cmd(new RemoveCSSPropertyCommand(document(), decl, property));
784 applyCommandToComposite(cmd);
787 void CompositeEditCommand::removeNodeAttribute(ElementImpl *element, int attribute)
789 DOMString value = element->getAttribute(attribute);
792 EditCommandPtr cmd(new RemoveNodeAttributeCommand(document(), element, attribute));
793 applyCommandToComposite(cmd);
796 void CompositeEditCommand::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
798 EditCommandPtr cmd(new SetNodeAttributeCommand(document(), element, attribute, value));
799 applyCommandToComposite(cmd);
802 void CompositeEditCommand::rebalanceWhitespace()
804 Selection selection = endingSelection();
805 if (selection.isCaretOrRange()) {
806 EditCommandPtr startCmd(new RebalanceWhitespaceCommand(document(), endingSelection().start()));
807 applyCommandToComposite(startCmd);
808 if (selection.isRange()) {
809 EditCommandPtr endCmd(new RebalanceWhitespaceCommand(document(), endingSelection().end()));
810 applyCommandToComposite(endCmd);
815 NodeImpl *CompositeEditCommand::applyTypingStyle(NodeImpl *child) const
817 // FIXME: This function should share code with ApplyStyleCommand::applyStyleIfNeeded
818 // and ApplyStyleCommand::computeStyleChange.
819 // Both function do similar work, and the common parts could be factored out.
821 // FIXME: Improve typing style.
822 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
824 // update document layout once before running the rest of the function
825 // so that we avoid the expense of updating before each and every call
826 // to check a computed style
827 document()->updateLayout();
829 StyleChange styleChange(document()->part()->typingStyle());
831 NodeImpl *childToAppend = child;
832 int exceptionCode = 0;
834 if (styleChange.applyItalic()) {
835 ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
836 ASSERT(exceptionCode == 0);
837 italicElement->appendChild(childToAppend, exceptionCode);
838 ASSERT(exceptionCode == 0);
839 childToAppend = italicElement;
842 if (styleChange.applyBold()) {
843 ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
844 ASSERT(exceptionCode == 0);
845 boldElement->appendChild(childToAppend, exceptionCode);
846 ASSERT(exceptionCode == 0);
847 childToAppend = boldElement;
850 if (styleChange.cssStyle().length() > 0) {
851 ElementImpl *styleElement = document()->createHTMLElement("SPAN", exceptionCode);
852 ASSERT(exceptionCode == 0);
853 styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
854 styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
855 styleElement->appendChild(childToAppend, exceptionCode);
856 ASSERT(exceptionCode == 0);
857 childToAppend = styleElement;
860 return childToAppend;
863 void CompositeEditCommand::deleteInsignificantText(TextImpl *textNode, int start, int end)
865 if (!textNode || !textNode->renderer() || start >= end)
868 RenderText *textRenderer = static_cast<RenderText *>(textNode->renderer());
869 InlineTextBox *box = textRenderer->firstTextBox();
871 // whole text node is empty
872 removeNode(textNode);
876 long length = textNode->length();
877 if (start >= length || end > length)
881 InlineTextBox *prevBox = 0;
882 DOMStringImpl *str = 0;
884 // This loop structure works to process all gaps preceding a box,
885 // and also will look at the gap after the last box.
886 while (prevBox || box) {
887 int gapStart = prevBox ? prevBox->m_start + prevBox->m_len : 0;
889 // No more chance for any intersections
892 int gapEnd = box ? box->m_start : length;
893 bool indicesIntersect = start <= gapEnd && end >= gapStart;
894 int gapLen = gapEnd - gapStart;
895 if (indicesIntersect && gapLen > 0) {
896 gapStart = kMax(gapStart, start);
897 gapEnd = kMin(gapEnd, end);
899 str = textNode->string()->substring(start, end - start);
902 // remove text in the gap
903 str->remove(gapStart - start - removed, gapLen);
909 box = box->nextTextBox();
913 // Replace the text between start and end with our pruned version.
915 replaceTextInNode(textNode, start, end - start, str);
918 // Assert that we are not going to delete all of the text in the node.
919 // If we were, that should have been done above with the call to
920 // removeNode and return.
921 ASSERT(start > 0 || (unsigned long)end - start < textNode->length());
922 deleteTextFromNode(textNode, start, end - start);
928 void CompositeEditCommand::deleteInsignificantText(const Position &start, const Position &end)
930 if (start.isNull() || end.isNull())
933 if (RangeImpl::compareBoundaryPoints(start, end) >= 0)
936 NodeImpl *node = start.node();
938 NodeImpl *next = node->traverseNextNode();
940 if (node->isTextNode()) {
941 TextImpl *textNode = static_cast<TextImpl *>(node);
942 bool isStartNode = node == start.node();
943 bool isEndNode = node == end.node();
944 int startOffset = isStartNode ? start.offset() : 0;
945 int endOffset = isEndNode ? end.offset() : textNode->length();
946 deleteInsignificantText(textNode, startOffset, endOffset);
949 if (node == end.node())
955 void CompositeEditCommand::deleteInsignificantTextDownstream(const DOM::Position &pos)
957 Position end = VisiblePosition(pos).next().deepEquivalent().downstream(StayInBlock);
958 deleteInsignificantText(pos, end);
961 void CompositeEditCommand::insertBlockPlaceholder(NodeImpl *node)
966 ASSERT(node->renderer() && node->renderer()->isBlockFlow());
968 appendNode(createBlockPlaceholderElement(document()), node);
971 bool CompositeEditCommand::insertBlockPlaceholderIfNeeded(NodeImpl *node)
976 document()->updateLayout();
978 RenderObject *renderer = node->renderer();
979 if (!renderer || !renderer->isBlockFlow())
982 if (renderer->height() > 0)
985 insertBlockPlaceholder(node);
989 bool CompositeEditCommand::removeBlockPlaceholderIfNeeded(NodeImpl *node)
994 document()->updateLayout();
996 RenderObject *renderer = node->renderer();
997 if (!renderer || !renderer->isBlockFlow())
1000 for (NodeImpl *checkMe = node; checkMe; checkMe = checkMe->traverseNextNode(node)) {
1001 if (checkMe->isElementNode()) {
1002 ElementImpl *element = static_cast<ElementImpl *>(checkMe);
1003 if (element->enclosingBlockFlowElement() == node &&
1004 element->getAttribute(ATTR_CLASS) == blockPlaceholderClassString()) {
1005 removeNode(element);
1014 void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position &pos)
1019 VisiblePosition visiblePos(pos);
1020 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
1021 VisiblePosition visibleParagraphEnd(endOfParagraph(visiblePos, IncludeLineBreak));
1022 Position paragraphStart = visibleParagraphStart.deepEquivalent().upstream(StayInBlock);
1023 Position paragraphEnd = visibleParagraphEnd.deepEquivalent().upstream(StayInBlock);
1024 Position beforeParagraphStart = paragraphStart.upstream(DoNotStayInBlock);
1026 // Perform some checks to see if we need to perform work in this function.
1027 if (paragraphStart.node()->isBlockFlow()) {
1028 if (paragraphEnd.node()->isBlockFlow()) {
1029 if (!paragraphEnd.node()->isAncestor(paragraphStart.node())) {
1030 // If the paragraph end is a descendant of paragraph start, then we need to run
1031 // the rest of this function. If not, we can bail here.
1035 else if (paragraphEnd.node()->enclosingBlockFlowElement() != paragraphStart.node()) {
1036 // The paragraph end is in another block that is an ancestor of the paragraph start.
1037 // We can bail as we have a full block to work with.
1038 ASSERT(paragraphStart.node()->isAncestor(paragraphEnd.node()->enclosingBlockFlowElement()));
1041 else if (visibleParagraphEnd.next().isNull()) {
1042 // At the end of the document. We can bail here as well.
1047 // Create the block to insert. Most times, this will be a shallow clone of the block containing
1048 // the start of the selection (the start block), except for two cases:
1049 // 1) When the start block is a body element.
1050 // 2) When the start block is a mail blockquote and we are not in a position to insert
1051 // the new block as a peer of the start block. This prevents creating an unwanted
1052 // additional level of quoting.
1053 NodeImpl *startBlock = paragraphStart.node()->enclosingBlockFlowElement();
1054 NodeImpl *newBlock = 0;
1055 if (startBlock->id() == ID_BODY || (isMailBlockquote(startBlock) && paragraphStart.node() != startBlock))
1056 newBlock = createDefaultParagraphElement(document());
1058 newBlock = startBlock->cloneNode(false);
1060 NodeImpl *moveNode = paragraphStart.node();
1061 if (paragraphStart.offset() >= paragraphStart.node()->caretMaxOffset())
1062 moveNode = moveNode->traverseNextNode();
1063 NodeImpl *endNode = paragraphEnd.node();
1064 while (moveNode && !moveNode->isBlockFlow()) {
1065 NodeImpl *next = moveNode->traverseNextNode();
1066 removeNode(moveNode);
1067 appendNode(moveNode, newBlock);
1068 if (moveNode == endNode)
1073 if (paragraphStart.node()->id() == ID_BODY) {
1074 insertNodeAt(newBlock, paragraphStart.node(), 0);
1076 else if (paragraphStart.node()->id() == ID_BR) {
1077 insertNodeAfter(newBlock, paragraphStart.node());
1079 else if (paragraphStart.node()->isBlockFlow()) {
1080 insertNodeBefore(newBlock, paragraphStart.node());
1082 else if (beforeParagraphStart.node()->enclosingBlockFlowElement()->id() != ID_BODY) {
1083 insertNodeAfter(newBlock, beforeParagraphStart.node()->enclosingBlockFlowElement());
1086 insertNodeAfter(newBlock, beforeParagraphStart.node());
1090 bool CompositeEditCommand::isMailBlockquote(const NodeImpl *node) const
1092 if (!node || !node->renderer() || !node->isElementNode() && node->id() != ID_BLOCKQUOTE)
1095 return static_cast<const ElementImpl *>(node)->getAttribute("type") == "cite";
1098 //==========================================================================================
1099 // Concrete commands
1100 //------------------------------------------------------------------------------------------
1101 // AppendNodeCommand
1103 AppendNodeCommand::AppendNodeCommand(DocumentImpl *document, NodeImpl *appendChild, NodeImpl *parentNode)
1104 : EditCommand(document), m_appendChild(appendChild), m_parentNode(parentNode)
1106 ASSERT(m_appendChild);
1107 m_appendChild->ref();
1109 ASSERT(m_parentNode);
1110 m_parentNode->ref();
1113 AppendNodeCommand::~AppendNodeCommand()
1115 ASSERT(m_appendChild);
1116 m_appendChild->deref();
1118 ASSERT(m_parentNode);
1119 m_parentNode->deref();
1122 void AppendNodeCommand::doApply()
1124 ASSERT(m_appendChild);
1125 ASSERT(m_parentNode);
1127 int exceptionCode = 0;
1128 m_parentNode->appendChild(m_appendChild, exceptionCode);
1129 ASSERT(exceptionCode == 0);
1132 void AppendNodeCommand::doUnapply()
1134 ASSERT(m_appendChild);
1135 ASSERT(m_parentNode);
1136 ASSERT(state() == Applied);
1138 int exceptionCode = 0;
1139 m_parentNode->removeChild(m_appendChild, exceptionCode);
1140 ASSERT(exceptionCode == 0);
1143 //------------------------------------------------------------------------------------------
1144 // ApplyStyleCommand
1146 ApplyStyleCommand::ApplyStyleCommand(DocumentImpl *document, CSSStyleDeclarationImpl *style, EditAction editingAction)
1147 : CompositeEditCommand(document), m_style(style->makeMutable()), m_editingAction(editingAction)
1153 ApplyStyleCommand::~ApplyStyleCommand()
1159 void ApplyStyleCommand::doApply()
1161 // apply the block-centric properties of the style
1162 CSSMutableStyleDeclarationImpl *blockStyle = m_style->copyBlockProperties();
1164 applyBlockStyle(blockStyle);
1166 // apply any remaining styles to the inline elements
1167 // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
1168 if (blockStyle->length() < m_style->length()) {
1169 CSSMutableStyleDeclarationImpl *inlineStyle = m_style->copy();
1171 applyRelativeFontStyleChange(inlineStyle);
1172 blockStyle->diff(inlineStyle);
1173 applyInlineStyle(inlineStyle);
1174 inlineStyle->deref();
1177 blockStyle->deref();
1179 setEndingSelectionNeedsLayout();
1182 EditAction ApplyStyleCommand::editingAction() const
1184 return m_editingAction;
1187 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclarationImpl *style)
1189 // update document layout once before removing styles
1190 // so that we avoid the expense of updating before each and every call
1191 // to check a computed style
1192 document()->updateLayout();
1194 // get positions we want to use for applying style
1195 Position start(endingSelection().start());
1196 Position end(endingSelection().end());
1198 // remove current values, if any, of the specified styles from the blocks
1199 // NOTE: tracks the previous block to avoid repeated processing
1200 NodeImpl *beyondEnd = end.node()->traverseNextNode();
1201 NodeImpl *prevBlock = 0;
1202 for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode()) {
1203 NodeImpl *block = node->enclosingBlockFlowElement();
1204 if (block != prevBlock && block->isHTMLElement()) {
1205 removeCSSStyle(style, static_cast<HTMLElementImpl *>(block));
1210 // apply specified styles to the block flow elements in the selected range
1212 for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode()) {
1213 if (node->renderer()) {
1214 NodeImpl *block = node->enclosingBlockFlowElement();
1215 if (block != prevBlock) {
1216 addBlockStyleIfNeeded(style, node);
1223 #define NoFontDelta (0.0f)
1224 #define MinimumFontSize (0.1f)
1226 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclarationImpl *style)
1228 if (style->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
1229 // Explicit font size overrides any delta.
1230 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1234 // Get the adjustment amount out of the style.
1235 CSSValueImpl *value = style->getPropertyCSSValue(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1239 float adjustment = NoFontDelta;
1240 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
1241 CSSPrimitiveValueImpl *primitiveValue = static_cast<CSSPrimitiveValueImpl *>(value);
1242 if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
1243 // Only PX handled now. If we handle more types in the future, perhaps
1244 // a switch statement here would be more appropriate.
1245 adjustment = primitiveValue->getFloatValue(CSSPrimitiveValue::CSS_PX);
1248 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1250 if (adjustment == NoFontDelta)
1253 // Adjust to the positions we want to use for applying style.
1254 Selection selection = endingSelection();
1255 Position start(selection.start().downstream(StayInBlock));
1256 Position end(selection.end().upstream(StayInBlock));
1257 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
1258 Position swap = start;
1263 // Join up any adjacent text nodes.
1264 if (start.node()->isTextNode()) {
1265 joinChildTextNodes(start.node()->parentNode(), start, end);
1266 selection = endingSelection();
1267 start = selection.start();
1268 end = selection.end();
1270 if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
1271 joinChildTextNodes(end.node()->parentNode(), start, end);
1272 selection = endingSelection();
1273 start = selection.start();
1274 end = selection.end();
1277 // Split the start text nodes if needed to apply style.
1278 bool splitStart = splitTextAtStartIfNeeded(start, end);
1280 start = endingSelection().start();
1281 end = endingSelection().end();
1283 bool splitEnd = splitTextAtEndIfNeeded(start, end);
1285 start = endingSelection().start();
1286 end = endingSelection().end();
1289 NodeImpl *beyondEnd = end.node()->traverseNextNode(); // Calculate loop end point.
1290 start = start.upstream(StayInBlock); // Move upstream to ensure we do not add redundant spans.
1292 // Store away font size before making any changes to the document.
1293 // This ensures that changes to one node won't effect another.
1294 QMap<const NodeImpl *,float> startingFontSizes;
1295 for (const NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode())
1296 startingFontSizes.insert(node, computedFontSize(node));
1298 // These spans were added by us. If empty after font size changes, they can be removed.
1299 QPtrList<NodeImpl> emptySpans;
1301 NodeImpl *lastStyledNode = 0;
1302 for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode()) {
1303 // Only work on fully selected nodes.
1304 if (!nodeFullySelected(node, start, end))
1307 HTMLElementImpl *elem = 0;
1308 if (node->isHTMLElement()) {
1309 elem = static_cast<HTMLElementImpl *>(node);
1311 else if (node->isTextNode() && node->parentNode() != lastStyledNode) {
1312 // Last styled node was not parent node of this text node, but we wish to style this
1313 // text node. To make this possible, add a style span to surround this text node.
1314 elem = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
1315 insertNodeBefore(elem, node);
1316 surroundNodeRangeWithElement(node, node, elem);
1319 // Only handle HTML elements and text nodes.
1322 lastStyledNode = node;
1324 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->getInlineStyleDecl();
1325 float currentFontSize = computedFontSize(node);
1326 float desiredFontSize = kMax(MinimumFontSize, startingFontSizes[node] + adjustment);
1327 if (inlineStyleDecl->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
1328 inlineStyleDecl->removeProperty(CSS_PROP_FONT_SIZE, true);
1329 currentFontSize = computedFontSize(node);
1331 if (currentFontSize != desiredFontSize) {
1332 QString desiredFontSizeString = QString::number(desiredFontSize);
1333 desiredFontSizeString += "px";
1334 inlineStyleDecl->setProperty(CSS_PROP_FONT_SIZE, desiredFontSizeString, false, false);
1335 setNodeAttribute(elem, ATTR_STYLE, inlineStyleDecl->cssText());
1337 if (inlineStyleDecl->length() == 0) {
1338 removeNodeAttribute(elem, ATTR_STYLE);
1339 if (isEmptyStyleSpan(elem))
1340 emptySpans.append(elem);
1344 for (QPtrListIterator<NodeImpl> it(emptySpans); it.current(); ++it)
1345 removeNodePreservingChildren(it.current());
1349 #undef MinimumFontSize
1351 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style)
1353 // adjust to the positions we want to use for applying style
1354 Position start(endingSelection().start().downstream(StayInBlock).equivalentRangeCompliantPosition());
1355 Position end(endingSelection().end().upstream(StayInBlock));
1356 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
1357 Position swap = start;
1362 // update document layout once before removing styles
1363 // so that we avoid the expense of updating before each and every call
1364 // to check a computed style
1365 document()->updateLayout();
1367 // Remove style from the selection.
1368 // Use the upstream position of the start for removing style.
1369 // This will ensure we remove all traces of the relevant styles from the selection
1370 // and prevent us from adding redundant ones, as described in:
1371 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
1372 removeInlineStyle(style, start.upstream(), end);
1374 // split the start node if the selection starts inside of it
1375 bool splitStart = splitTextAtStartIfNeeded(start, end);
1377 start = endingSelection().start();
1378 end = endingSelection().end();
1381 // split the end node if the selection ends inside of it
1382 splitTextAtEndIfNeeded(start, end);
1383 start = endingSelection().start();
1384 end = endingSelection().end();
1386 // update document layout once before running the rest of the function
1387 // so that we avoid the expense of updating before each and every call
1388 // to check a computed style
1389 document()->updateLayout();
1391 if (start.node() == end.node()) {
1392 // simple case...start and end are the same node
1393 addInlineStyleIfNeeded(style, start.node(), end.node());
1396 NodeImpl *node = start.node();
1398 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
1399 NodeImpl *runStart = node;
1401 NodeImpl *next = node->traverseNextNode();
1402 // Break if node is the end node, or if the next node does not fit in with
1403 // the current group.
1404 if (node == end.node() ||
1405 runStart->parentNode() != next->parentNode() ||
1406 (next->isHTMLElement() && next->id() != ID_BR) ||
1407 (next->renderer() && !next->renderer()->isInline()))
1411 // Now apply style to the run we found.
1412 addInlineStyleIfNeeded(style, runStart, node);
1414 if (node == end.node())
1416 node = node->traverseNextNode();
1421 //------------------------------------------------------------------------------------------
1422 // ApplyStyleCommand: style-removal helpers
1424 bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1426 QValueListConstIterator<CSSProperty> end;
1427 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1428 switch ((*it).id()) {
1429 case CSS_PROP_FONT_WEIGHT:
1430 if (elem->id() == ID_B)
1433 case CSS_PROP_FONT_STYLE:
1434 if (elem->id() == ID_I)
1443 void ApplyStyleCommand::removeHTMLStyleNode(HTMLElementImpl *elem)
1445 // This node can be removed.
1446 // EDIT FIXME: This does not handle the case where the node
1447 // has attributes. But how often do people add attributes to <B> tags?
1448 // Not so often I think.
1450 removeNodePreservingChildren(elem);
1453 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1458 CSSMutableStyleDeclarationImpl *decl = elem->inlineStyleDecl();
1462 QValueListConstIterator<CSSProperty> end;
1463 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1464 int propertyID = (*it).id();
1465 CSSValueImpl *value = decl->getPropertyCSSValue(propertyID);
1468 removeCSSProperty(decl, propertyID);
1473 if (elem->id() == ID_SPAN && elem->renderer() && elem->renderer()->isInline()) {
1474 // Check to see if the span is one we added to apply style.
1475 // If it is, and there are no more attributes on the span other than our
1476 // class marker, remove the span.
1477 if (decl->length() == 0) {
1478 removeNodeAttribute(elem, ATTR_STYLE);
1479 NamedAttrMapImpl *map = elem->attributes();
1480 if (map && map->length() == 1 && elem->getAttribute(ATTR_CLASS) == styleSpanClassString())
1481 removeNodePreservingChildren(elem);
1486 void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
1488 ASSERT(start.isNotNull());
1489 ASSERT(end.isNotNull());
1490 ASSERT(start.node()->inDocument());
1491 ASSERT(end.node()->inDocument());
1492 ASSERT(RangeImpl::compareBoundaryPoints(start, end) <= 0);
1496 void ApplyStyleCommand::removeInlineStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
1498 ASSERT(start.isNotNull());
1499 ASSERT(end.isNotNull());
1500 ASSERT(start.node()->inDocument());
1501 ASSERT(end.node()->inDocument());
1502 ASSERT(RangeImpl::compareBoundaryPoints(start, end) <= 0);
1504 NodeImpl *node = start.node();
1506 NodeImpl *next = node->traverseNextNode();
1507 if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
1508 HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
1509 if (isHTMLStyleNode(style, elem))
1510 removeHTMLStyleNode(elem);
1512 removeCSSStyle(style, elem);
1514 if (node == end.node())
1520 bool ApplyStyleCommand::nodeFullySelected(NodeImpl *node, const Position &start, const Position &end) const
1524 Position pos = Position(node, node->childNodeCount()).upstream();
1525 return RangeImpl::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 &&
1526 RangeImpl::compareBoundaryPoints(pos, end) <= 0;
1529 //------------------------------------------------------------------------------------------
1530 // ApplyStyleCommand: style-application helpers
1533 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
1535 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
1536 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
1537 TextImpl *text = static_cast<TextImpl *>(start.node());
1538 EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, start.offset()));
1539 applyCommandToComposite(cmd);
1540 setEndingSelection(Selection(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment)));
1546 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
1548 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
1549 TextImpl *text = static_cast<TextImpl *>(end.node());
1550 SplitTextNodeCommand *impl = new SplitTextNodeCommand(document(), text, end.offset());
1551 EditCommandPtr cmd(impl);
1552 applyCommandToComposite(cmd);
1553 NodeImpl *prevNode = impl->node()->previousSibling();
1555 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
1557 setEndingSelection(Selection(Position(startNode, start.offset()), Position(prevNode, prevNode->caretMaxOffset())));
1563 void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
1569 NodeImpl *node = startNode;
1571 NodeImpl *next = node->traverseNextNode();
1572 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
1574 appendNode(node, element);
1576 if (node == endNode)
1582 void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *node)
1584 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1589 HTMLElementImpl *block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
1593 StyleChange styleChange(style, Position(block, 0), StyleChange::DoNotUseLegacyHTMLStyles);
1594 if (styleChange.cssStyle().length() > 0) {
1595 moveParagraphContentsToNewBlockIfNecessary(Position(node, 0));
1596 block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
1597 DOMString cssText = styleChange.cssStyle();
1598 CSSMutableStyleDeclarationImpl *decl = block->inlineStyleDecl();
1600 cssText += decl->cssText();
1601 setNodeAttribute(block, ATTR_STYLE, cssText);
1605 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *startNode, NodeImpl *endNode)
1607 // FIXME: This function should share code with CompositeEditCommand::applyTypingStyle.
1608 // Both functions do similar work, and the common parts could be factored out.
1610 StyleChange styleChange(style, Position(startNode, 0));
1611 int exceptionCode = 0;
1613 if (styleChange.cssStyle().length() > 0) {
1614 ElementImpl *styleElement = document()->createHTMLElement("SPAN", exceptionCode);
1615 ASSERT(exceptionCode == 0);
1616 styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
1617 styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
1618 insertNodeBefore(styleElement, startNode);
1619 surroundNodeRangeWithElement(startNode, endNode, styleElement);
1622 if (styleChange.applyBold()) {
1623 ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
1624 ASSERT(exceptionCode == 0);
1625 insertNodeBefore(boldElement, startNode);
1626 surroundNodeRangeWithElement(startNode, endNode, boldElement);
1629 if (styleChange.applyItalic()) {
1630 ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
1631 ASSERT(exceptionCode == 0);
1632 insertNodeBefore(italicElement, startNode);
1633 surroundNodeRangeWithElement(startNode, endNode, italicElement);
1637 Position ApplyStyleCommand::positionInsertionPoint(Position pos)
1639 if (pos.node()->isTextNode() && (pos.offset() > 0 && pos.offset() < pos.node()->maxOffset())) {
1640 SplitTextNodeCommand *impl = new SplitTextNodeCommand(document(), static_cast<TextImpl *>(pos.node()), pos.offset());
1641 EditCommandPtr split(impl);
1643 pos = Position(impl->node(), 0);
1647 // EDIT FIXME: If modified to work with the internals of applying style,
1648 // this code can work to optimize cases where a style change is taking place on
1649 // a boundary between nodes where one of the nodes has the desired style. In other
1650 // words, it is possible for content to be merged into existing nodes rather than adding
1651 // additional markup.
1652 if (currentlyHasStyle(pos))
1656 if (pos.offset() >= pos.node()->caretMaxOffset()) {
1657 NodeImpl *nextNode = pos.node()->traverseNextNode();
1659 Position next = Position(nextNode, 0);
1660 if (currentlyHasStyle(next))
1665 // try previous node
1666 if (pos.offset() <= pos.node()->caretMinOffset()) {
1667 NodeImpl *prevNode = pos.node()->traversePreviousNode();
1669 Position prev = Position(prevNode, prevNode->maxOffset());
1670 if (currentlyHasStyle(prev))
1679 float ApplyStyleCommand::computedFontSize(const NodeImpl *node)
1686 Position pos(const_cast<NodeImpl *>(node), 0);
1687 CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
1690 computedStyle->ref();
1692 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE));
1695 size = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
1699 computedStyle->deref();
1703 void ApplyStyleCommand::joinChildTextNodes(NodeImpl *node, const Position &start, const Position &end)
1708 Position newStart = start;
1709 Position newEnd = end;
1711 NodeImpl *child = node->firstChild();
1713 NodeImpl *next = child->nextSibling();
1714 if (child->isTextNode() && next && next->isTextNode()) {
1715 TextImpl *childText = static_cast<TextImpl *>(child);
1716 TextImpl *nextText = static_cast<TextImpl *>(next);
1717 if (next == start.node())
1718 newStart = Position(childText, childText->length() + start.offset());
1719 if (next == end.node())
1720 newEnd = Position(childText, childText->length() + end.offset());
1721 DOMString textToMove = nextText->data();
1722 insertTextIntoNode(childText, childText->length(), textToMove);
1724 // don't move child node pointer. it may want to merge with more text nodes.
1727 child = child->nextSibling();
1731 setEndingSelection(Selection(newStart, newEnd));
1734 //------------------------------------------------------------------------------------------
1735 // DeleteFromTextNodeCommand
1737 DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(DocumentImpl *document, TextImpl *node, long offset, long count)
1738 : EditCommand(document), m_node(node), m_offset(offset), m_count(count)
1741 ASSERT(m_offset >= 0);
1742 ASSERT(m_offset < (long)m_node->length());
1743 ASSERT(m_count >= 0);
1748 DeleteFromTextNodeCommand::~DeleteFromTextNodeCommand()
1754 void DeleteFromTextNodeCommand::doApply()
1758 int exceptionCode = 0;
1759 m_text = m_node->substringData(m_offset, m_count, exceptionCode);
1760 ASSERT(exceptionCode == 0);
1762 m_node->deleteData(m_offset, m_count, exceptionCode);
1763 ASSERT(exceptionCode == 0);
1766 void DeleteFromTextNodeCommand::doUnapply()
1769 ASSERT(!m_text.isEmpty());
1771 int exceptionCode = 0;
1772 m_node->insertData(m_offset, m_text, exceptionCode);
1773 ASSERT(exceptionCode == 0);
1776 //------------------------------------------------------------------------------------------
1777 // DeleteSelectionCommand
1779 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, bool smartDelete, bool mergeBlocksAfterDelete)
1780 : CompositeEditCommand(document),
1781 m_hasSelectionToDelete(false),
1782 m_smartDelete(smartDelete),
1783 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
1791 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
1792 : CompositeEditCommand(document),
1793 m_hasSelectionToDelete(true),
1794 m_smartDelete(smartDelete),
1795 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
1796 m_selectionToDelete(selection),
1804 void DeleteSelectionCommand::initializePositionData()
1807 // Handle setting some basic positions
1809 Position start = m_selectionToDelete.start();
1810 Position end = m_selectionToDelete.end();
1812 m_upstreamStart = start.upstream(StayInBlock);
1813 m_downstreamStart = start.downstream(StayInBlock);
1814 m_upstreamEnd = end.upstream(StayInBlock);
1815 m_downstreamEnd = end.downstream(StayInBlock);
1818 // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
1820 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition();
1821 bool hasLeadingWhitespaceBeforeAdjustment = m_leadingWhitespace.isNotNull();
1822 if (m_smartDelete && hasLeadingWhitespaceBeforeAdjustment) {
1823 Position pos = VisiblePosition(start).previous().deepEquivalent();
1824 // Expand out one character upstream for smart delete and recalculate
1825 // positions based on this change.
1826 m_upstreamStart = pos.upstream(StayInBlock);
1827 m_downstreamStart = pos.downstream(StayInBlock);
1828 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition();
1830 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition();
1831 // Note: trailing whitespace is only considered for smart delete if there is no leading
1832 // whitespace, as in the case where you double-click the first word of a paragraph.
1833 if (m_smartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_trailingWhitespace.isNotNull()) {
1834 // Expand out one character downstream for smart delete and recalculate
1835 // positions based on this change.
1836 Position pos = VisiblePosition(end).next().deepEquivalent();
1837 m_upstreamEnd = pos.upstream(StayInBlock);
1838 m_downstreamEnd = pos.downstream(StayInBlock);
1839 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition();
1841 m_trailingWhitespaceValid = true;
1844 // Handle setting start and end blocks and the start node.
1846 m_startBlock = m_downstreamStart.node()->enclosingBlockFlowElement();
1847 m_startBlock->ref();
1848 m_endBlock = m_upstreamEnd.node()->enclosingBlockFlowElement();
1850 m_startNode = m_upstreamStart.node();
1854 // Handle detecting if the line containing the selection end is itself fully selected.
1855 // This is one of the tests that determines if block merging of content needs to be done.
1857 VisiblePosition visibleEnd(end);
1858 if (isFirstVisiblePositionInParagraph(visibleEnd) || isLastVisiblePositionInParagraph(visibleEnd)) {
1859 Position previousLineStart = previousLinePosition(visibleEnd, DOWNSTREAM, 0).deepEquivalent();
1860 if (previousLineStart.isNull() || RangeImpl::compareBoundaryPoints(previousLineStart, m_downstreamStart) >= 0)
1861 m_mergeBlocksAfterDelete = false;
1864 debugPosition("m_upstreamStart ", m_upstreamStart);
1865 debugPosition("m_downstreamStart ", m_downstreamStart);
1866 debugPosition("m_upstreamEnd ", m_upstreamEnd);
1867 debugPosition("m_downstreamEnd ", m_downstreamEnd);
1868 debugPosition("m_leadingWhitespace ", m_leadingWhitespace);
1869 debugPosition("m_trailingWhitespace ", m_trailingWhitespace);
1870 debugNode( "m_startBlock ", m_startBlock);
1871 debugNode( "m_endBlock ", m_endBlock);
1872 debugNode( "m_startNode ", m_startNode);
1875 void DeleteSelectionCommand::insertPlaceholderForAncestorBlockContent()
1877 // This code makes sure a line does not disappear when deleting in this case:
1878 // <p>foo</p>bar<p>baz</p>
1879 // Select "bar" and hit delete. If nothing is done, the line containing bar will disappear.
1880 // It needs to be held open by inserting a placeholder.
1882 // <rdar://problem/3928305> selecting an entire line and typing over causes new inserted text at top of document
1884 // The checks below detect the case where the selection contains content in an ancestor block
1885 // surrounded by child blocks.
1887 NodeImpl *upstreamBlock = m_upstreamStart.node()->enclosingBlockFlowElement();
1888 NodeImpl *beforeUpstreamBlock = m_upstreamStart.upstream().node()->enclosingBlockFlowElement();
1890 if (upstreamBlock != beforeUpstreamBlock && beforeUpstreamBlock->isAncestor(upstreamBlock)) {
1891 NodeImpl *downstreamBlock = m_downstreamEnd.node()->enclosingBlockFlowElement();
1892 NodeImpl *afterDownstreamBlock = m_downstreamEnd.downstream().node()->enclosingBlockFlowElement();
1894 if (afterDownstreamBlock != downstreamBlock && afterDownstreamBlock != upstreamBlock) {
1895 NodeImpl *block = createDefaultParagraphElement(document());
1896 insertNodeBefore(block, m_upstreamStart.node());
1897 insertBlockPlaceholderIfNeeded(block);
1898 m_endingPosition = Position(block, 0);
1903 void DeleteSelectionCommand::saveTypingStyleState()
1905 // Figure out the typing style in effect before the delete is done.
1906 // FIXME: Improve typing style.
1907 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
1908 CSSComputedStyleDeclarationImpl *computedStyle = m_selectionToDelete.start().computedStyle();
1909 computedStyle->ref();
1910 m_typingStyle = computedStyle->copyInheritableProperties();
1911 m_typingStyle->ref();
1912 computedStyle->deref();
1915 bool DeleteSelectionCommand::handleSpecialCaseAllContentDelete()
1917 Position start = m_downstreamStart;
1918 Position end = m_upstreamEnd;
1920 ElementImpl *rootElement = start.node()->rootEditableElement();
1921 Position rootStart = Position(rootElement, 0);
1922 Position rootEnd = Position(rootElement, rootElement ? rootElement->childNodeCount() : 0).equivalentDeepPosition();
1923 if (start == VisiblePosition(rootStart).downstreamDeepEquivalent() && end == VisiblePosition(rootEnd).deepEquivalent()) {
1924 // Delete every child of the root editable element
1925 NodeImpl *node = rootElement->firstChild();
1927 NodeImpl *next = node->traverseNextSibling();
1936 bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
1938 // Check for special-case where the selection contains only a BR on a line by itself after another BR.
1939 bool upstreamStartIsBR = m_startNode->id() == ID_BR;
1940 bool downstreamStartIsBR = m_downstreamStart.node()->id() == ID_BR;
1941 bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
1942 if (isBROnLineByItself) {
1943 removeNode(m_downstreamStart.node());
1944 m_endingPosition = m_upstreamStart;
1945 m_mergeBlocksAfterDelete = false;
1949 // Check for special-case where the selection contains only a BR right after a block ended.
1950 bool downstreamEndIsBR = m_downstreamEnd.node()->id() == ID_BR;
1951 Position upstreamFromBR = m_downstreamEnd.upstream();
1952 Position downstreamFromStart = m_downstreamStart.downstream();
1953 bool startIsBRAfterBlock = downstreamEndIsBR && downstreamFromStart.node() == m_downstreamEnd.node() &&
1954 m_downstreamEnd.node()->enclosingBlockFlowElement() != upstreamFromBR.node()->enclosingBlockFlowElement();
1955 if (startIsBRAfterBlock) {
1956 removeNode(m_downstreamEnd.node());
1957 m_endingPosition = upstreamFromBR;
1958 m_mergeBlocksAfterDelete = false;
1962 // Not a special-case delete per se, but we can detect that the merging of content between blocks
1963 // should not be done.
1964 if (upstreamStartIsBR && downstreamStartIsBR)
1965 m_mergeBlocksAfterDelete = false;
1970 void DeleteSelectionCommand::handleGeneralDelete()
1972 int startOffset = m_upstreamStart.offset();
1974 if (startOffset == 0 && m_startNode->isBlockFlow() && m_startBlock != m_endBlock && !m_endBlock->isAncestor(m_startBlock)) {
1975 // The block containing the start of the selection is completely selected.
1976 // Delete it all in one step right here.
1977 ASSERT(!m_downstreamEnd.node()->isAncestor(m_startNode));
1979 // shift the start node to the start of the next block.
1980 NodeImpl *old = m_startNode;
1981 m_startNode = m_startBlock->traverseNextSibling();
1986 removeFullySelectedNode(m_startBlock);
1988 else if (startOffset >= m_startNode->caretMaxOffset()) {
1989 // Move the start node to the next node in the tree since the startOffset is equal to
1990 // or beyond the start node's caretMaxOffset This means there is nothing visible to delete.
1991 // However, before moving on, delete any insignificant text that may be present in a text node.
1992 if (m_startNode->isTextNode()) {
1993 // Delete any insignificant text from this node.
1994 TextImpl *text = static_cast<TextImpl *>(m_startNode);
1995 if (text->length() > (unsigned)m_startNode->caretMaxOffset())
1996 deleteTextFromNode(text, m_startNode->caretMaxOffset(), text->length() - m_startNode->caretMaxOffset());
1999 // shift the start node to the next
2000 NodeImpl *old = m_startNode;
2001 m_startNode = old->traverseNextNode();
2007 if (m_startNode == m_downstreamEnd.node()) {
2008 // The selection to delete is all in one node.
2009 if (!m_startNode->renderer() ||
2010 (startOffset <= m_startNode->caretMinOffset() && m_downstreamEnd.offset() >= m_startNode->caretMaxOffset())) {
2012 removeFullySelectedNode(m_startNode);
2014 else if (m_downstreamEnd.offset() - startOffset > 0) {
2015 // in a text node that needs to be trimmed
2016 TextImpl *text = static_cast<TextImpl *>(m_startNode);
2017 deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset);
2018 m_trailingWhitespaceValid = false;
2022 // The selection to delete spans more than one node.
2023 NodeImpl *node = m_startNode;
2025 if (startOffset > 0) {
2026 // in a text node that needs to be trimmed
2027 TextImpl *text = static_cast<TextImpl *>(node);
2028 deleteTextFromNode(text, startOffset, text->length() - startOffset);
2029 node = node->traverseNextNode();
2032 // handle deleting all nodes that are completely selected
2033 while (node && node != m_downstreamEnd.node()) {
2034 if (!m_downstreamEnd.node()->isAncestor(node)) {
2035 NodeImpl *nextNode = node->traverseNextSibling();
2036 removeFullySelectedNode(node);
2040 NodeImpl *n = node->lastChild();
2041 while (n && n->lastChild())
2043 if (n == m_downstreamEnd.node() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMaxOffset()) {
2044 // remove an ancestor of m_downstreamEnd.node(), and thus m_downstreamEnd.node() itself
2045 removeFullySelectedNode(node);
2046 m_trailingWhitespaceValid = false;
2050 node = node->traverseNextNode();
2055 if (m_downstreamEnd.node() != m_startNode && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMinOffset()) {
2056 if (m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMaxOffset()) {
2057 // need to delete whole node
2058 // we can get here if this is the last node in the block
2059 removeFullySelectedNode(m_downstreamEnd.node());
2060 m_trailingWhitespaceValid = false;
2063 // in a text node that needs to be trimmed
2064 TextImpl *text = static_cast<TextImpl *>(m_downstreamEnd.node());
2065 if (m_downstreamEnd.offset() > 0) {
2066 deleteTextFromNode(text, 0, m_downstreamEnd.offset());
2067 m_downstreamEnd = Position(text, 0);
2068 m_trailingWhitespaceValid = false;
2075 void DeleteSelectionCommand::fixupWhitespace()
2077 document()->updateLayout();
2078 if (m_leadingWhitespace.isNotNull() && (m_trailingWhitespace.isNotNull() || !m_leadingWhitespace.isRenderedCharacter())) {
2079 LOG(Editing, "replace leading");
2080 TextImpl *textNode = static_cast<TextImpl *>(m_leadingWhitespace.node());
2081 replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
2083 else if (m_trailingWhitespace.isNotNull()) {
2084 if (m_trailingWhitespaceValid) {
2085 if (!m_trailingWhitespace.isRenderedCharacter()) {
2086 LOG(Editing, "replace trailing [valid]");
2087 TextImpl *textNode = static_cast<TextImpl *>(m_trailingWhitespace.node());
2088 replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
2092 Position pos = m_endingPosition.downstream(StayInBlock);
2093 pos = Position(pos.node(), pos.offset() - 1);
2094 if (isWS(pos) && !pos.isRenderedCharacter()) {
2095 LOG(Editing, "replace trailing [invalid]");
2096 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
2097 replaceTextInNode(textNode, pos.offset(), 1, nonBreakingSpaceString());
2098 // need to adjust ending position since the trailing position is not valid.
2099 m_endingPosition = pos;
2105 // This function moves nodes in the block containing startNode to dstBlock, starting
2106 // from startNode and proceeding to the end of the paragraph. Nodes in the block containing
2107 // startNode that appear in document order before startNode are not moved.
2108 // This function is an important helper for deleting selections that cross paragraph
2110 void DeleteSelectionCommand::moveNodesAfterNode()
2112 if (!m_mergeBlocksAfterDelete)
2115 if (m_endBlock == m_startBlock)
2118 NodeImpl *startNode = m_downstreamEnd.node();
2119 NodeImpl *dstNode = m_upstreamStart.node();
2121 if (!startNode->inDocument() || !dstNode->inDocument())
2124 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
2125 if (isTableStructureNode(startBlock))
2126 // Do not move content between parts of a table
2129 // Now that we are about to add content, check to see if a placeholder element
2131 removeBlockPlaceholderIfNeeded(startBlock);
2133 // Move the subtree containing node
2134 NodeImpl *node = startNode->enclosingInlineElement();
2136 // Insert after the subtree containing destNode
2137 NodeImpl *refNode = dstNode->enclosingInlineElement();
2139 // Nothing to do if start is already at the beginning of dstBlock
2140 NodeImpl *dstBlock = refNode->enclosingBlockFlowElement();
2141 if (startBlock == dstBlock->firstChild())
2145 NodeImpl *rootNode = refNode->rootEditableElement();
2146 while (node && node->isAncestor(startBlock)) {
2147 NodeImpl *moveNode = node;
2148 node = node->nextSibling();
2149 removeNode(moveNode);
2150 if (moveNode->id() == ID_BR && !moveNode->renderer()) {
2151 // Just remove this node, and don't put it back.
2152 // If the BR was not rendered (since it was at the end of a block, for instance),
2153 // putting it back in the document might make it appear, and that is not desirable.
2156 if (refNode == rootNode)
2157 insertNodeAt(moveNode, refNode, 0);
2159 insertNodeAfter(moveNode, refNode);
2161 if (moveNode->id() == ID_BR)
2165 // If the startBlock no longer has any kids, we may need to deal with adding a BR
2166 // to make the layout come out right. Consider this document:
2172 // Placing the insertion before before the 'T' of 'Two' and hitting delete will
2173 // move the contents of the div to the block containing 'One' and delete the div.
2174 // This will have the side effect of moving 'Three' on to the same line as 'One'
2175 // and 'Two'. This is undesirable. We fix this up by adding a BR before the 'Three'.
2176 // This may not be ideal, but it is better than nothing.
2177 document()->updateLayout();
2178 if (!startBlock->renderer() || !startBlock->renderer()->firstChild()) {
2179 removeNode(startBlock);
2180 document()->updateLayout();
2181 if (refNode->renderer() && refNode->renderer()->inlineBox() && refNode->renderer()->inlineBox()->nextOnLineExists()) {
2182 insertNodeAfter(createBreakElement(document()), refNode);
2187 void DeleteSelectionCommand::calculateEndingPosition()
2189 if (m_endingPosition.isNotNull() && m_endingPosition.node()->inDocument())
2192 m_endingPosition = m_upstreamStart;
2193 if (m_endingPosition.node()->inDocument())
2196 m_endingPosition = m_downstreamEnd;
2197 if (m_endingPosition.node()->inDocument())
2200 m_endingPosition = Position(m_startBlock, 0);
2201 if (m_endingPosition.node()->inDocument())
2204 m_endingPosition = Position(m_endBlock, 0);
2205 if (m_endingPosition.node()->inDocument())
2208 m_endingPosition = Position(document()->documentElement(), 0);
2211 void DeleteSelectionCommand::calculateTypingStyleAfterDelete(bool insertedPlaceholder)
2213 // Compute the difference between the style before the delete and the style now
2214 // after the delete has been done. Set this style on the part, so other editing
2215 // commands being composed with this one will work, and also cache it on the command,
2216 // so the KHTMLPart::appliedEditing can set it after the whole composite command
2218 // FIXME: Improve typing style.
2219 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2220 CSSComputedStyleDeclarationImpl endingStyle(m_endingPosition.node());
2221 endingStyle.diff(m_typingStyle);
2222 if (!m_typingStyle->length()) {
2223 m_typingStyle->deref();
2226 if (insertedPlaceholder && m_typingStyle) {
2227 // Apply style to the placeholder. This makes sure that the single line in the
2228 // paragraph has the right height, and that the paragraph takes on the style
2229 // of the preceding line and retains it even if you click away, click back, and
2230 // then start typing. In this case, the typing style is applied right now, and
2231 // is not retained until the next typing action.
2232 Position pastPlaceholder = endOfParagraph(VisiblePosition(m_endingPosition)).deepEquivalent();
2233 setEndingSelection(Selection(m_endingPosition, pastPlaceholder));
2234 applyStyle(m_typingStyle, EditActionUnspecified);
2235 m_typingStyle->deref();
2238 // Set m_typingStyle as the typing style.
2239 // It's perfectly OK for m_typingStyle to be null.
2240 document()->part()->setTypingStyle(m_typingStyle);
2241 setTypingStyle(m_typingStyle);
2244 void DeleteSelectionCommand::clearTransientState()
2246 m_selectionToDelete.clear();
2247 m_upstreamStart.clear();
2248 m_downstreamStart.clear();
2249 m_upstreamEnd.clear();
2250 m_downstreamEnd.clear();
2251 m_endingPosition.clear();
2252 m_leadingWhitespace.clear();
2253 m_trailingWhitespace.clear();
2256 m_startBlock->deref();
2260 m_endBlock->deref();
2264 m_startNode->deref();
2267 if (m_typingStyle) {
2268 m_typingStyle->deref();
2273 void DeleteSelectionCommand::doApply()
2275 // If selection has not been set to a custom selection when the command was created,
2276 // use the current ending selection.
2277 if (!m_hasSelectionToDelete)
2278 m_selectionToDelete = endingSelection();
2280 if (!m_selectionToDelete.isRange())
2283 initializePositionData();
2285 if (!m_startBlock || !m_endBlock) {
2286 // Can't figure out what blocks we're in. This can happen if
2287 // the document structure is not what we are expecting, like if
2288 // the document has no body element, or if the editable block
2289 // has been changed to display: inline. Some day it might
2290 // be nice to be able to deal with this, but for now, bail.
2291 clearTransientState();
2295 // Delete any text that may hinder our ability to fixup whitespace after the detele
2296 deleteInsignificantTextDownstream(m_trailingWhitespace);
2298 saveTypingStyleState();
2299 insertPlaceholderForAncestorBlockContent();
2301 if (!handleSpecialCaseAllContentDelete())
2302 if (!handleSpecialCaseBRDelete())
2303 handleGeneralDelete();
2305 // Do block merge if start and end of selection are in different blocks.
2306 moveNodesAfterNode();
2308 calculateEndingPosition();
2311 // If the delete emptied a block, add in a placeholder so the block does not
2312 // seem to disappear.
2313 bool insertedPlaceholder = insertBlockPlaceholderIfNeeded(m_endingPosition.node());
2314 calculateTypingStyleAfterDelete(insertedPlaceholder);
2315 debugPosition("endingPosition ", m_endingPosition);
2316 setEndingSelection(m_endingPosition);
2317 clearTransientState();
2318 rebalanceWhitespace();
2321 EditAction DeleteSelectionCommand::editingAction() const
2323 // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
2324 // but in that case there's a TypingCommand that supplies the editingAction(), so
2325 // the Undo menu correctly shows "Undo Typing"
2326 return EditActionCut;
2329 bool DeleteSelectionCommand::preservesTypingStyle() const
2334 //------------------------------------------------------------------------------------------
2335 // InsertIntoTextNode
2337 InsertIntoTextNode::InsertIntoTextNode(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
2338 : EditCommand(document), m_node(node), m_offset(offset)
2341 ASSERT(m_offset >= 0);
2342 ASSERT(!text.isEmpty());
2345 m_text = text.copy(); // make a copy to ensure that the string never changes
2348 InsertIntoTextNode::~InsertIntoTextNode()
2354 void InsertIntoTextNode::doApply()
2357 ASSERT(m_offset >= 0);
2358 ASSERT(!m_text.isEmpty());
2360 int exceptionCode = 0;
2361 m_node->insertData(m_offset, m_text, exceptionCode);
2362 ASSERT(exceptionCode == 0);
2365 void InsertIntoTextNode::doUnapply()
2368 ASSERT(m_offset >= 0);
2369 ASSERT(!m_text.isEmpty());
2371 int exceptionCode = 0;
2372 m_node->deleteData(m_offset, m_text.length(), exceptionCode);
2373 ASSERT(exceptionCode == 0);
2376 //------------------------------------------------------------------------------------------
2377 // InsertLineBreakCommand
2379 InsertLineBreakCommand::InsertLineBreakCommand(DocumentImpl *document)
2380 : CompositeEditCommand(document)
2384 bool InsertLineBreakCommand::preservesTypingStyle() const
2389 void InsertLineBreakCommand::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
2391 // Insert the BR after the caret position. In the case the
2392 // position is a block, do an append. We don't want to insert
2393 // the BR *after* the block.
2394 Position upstream(pos.upstream(StayInBlock));
2395 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
2396 if (cb == pos.node())
2397 appendNode(node, cb);
2399 insertNodeAfter(node, pos.node());
2402 void InsertLineBreakCommand::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
2404 // Insert the BR after the caret position. In the case the
2405 // position is a block, do an append. We don't want to insert
2406 // the BR *before* the block.
2407 Position upstream(pos.upstream(StayInBlock));
2408 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
2409 if (cb == pos.node())
2410 appendNode(node, cb);
2412 insertNodeBefore(node, pos.node());
2415 void InsertLineBreakCommand::doApply()
2418 Selection selection = endingSelection();
2420 ElementImpl *breakNode = createBreakElement(document());
2421 NodeImpl *nodeToInsert = breakNode;
2423 // Handle the case where there is a typing style.
2424 // FIXME: Improve typing style.
2425 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2426 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
2427 if (typingStyle && typingStyle->length() > 0)
2428 nodeToInsert = applyTypingStyle(breakNode);
2430 Position pos(selection.start().upstream(StayInBlock));
2431 bool atStart = pos.offset() <= pos.node()->caretMinOffset();
2432 bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
2433 bool atEndOfBlock = isLastVisiblePositionInBlock(VisiblePosition(pos));
2436 LOG(Editing, "input newline case 1");
2437 // Check for a trailing BR. If there isn't one, we'll need to insert an "extra" one.
2438 // This makes the "real" BR we want to insert appear in the rendering without any
2439 // significant side effects (and no real worries either since you can't arrow past
2441 if (pos.node()->id() == ID_BR && pos.offset() == 0) {
2442 // Already placed in a trailing BR. Insert "real" BR before it and leave the selection alone.
2443 insertNodeBefore(nodeToInsert, pos.node());
2446 NodeImpl *next = pos.node()->traverseNextNode();
2447 bool hasTrailingBR = next && next->id() == ID_BR && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
2448 insertNodeAfterPosition(nodeToInsert, pos);
2449 if (hasTrailingBR) {
2450 setEndingSelection(Position(next, 0));
2452 else if (!document()->inStrictMode()) {
2453 // Insert an "extra" BR at the end of the block.
2454 ElementImpl *extraBreakNode = createBreakElement(document());
2455 insertNodeAfter(extraBreakNode, nodeToInsert);
2456 setEndingSelection(Position(extraBreakNode, 0));
2461 LOG(Editing, "input newline case 2");
2462 // Insert node before downstream position, and place caret there as well.
2463 Position endingPosition = pos.downstream(StayInBlock);
2464 insertNodeBeforePosition(nodeToInsert, endingPosition);
2465 setEndingSelection(endingPosition);
2468 LOG(Editing, "input newline case 3");
2469 // Insert BR after this node. Place caret in the position that is downstream
2470 // of the current position, reckoned before inserting the BR in between.
2471 Position endingPosition = pos.downstream(StayInBlock);
2472 insertNodeAfterPosition(nodeToInsert, pos);
2473 setEndingSelection(endingPosition);
2476 // Split a text node
2477 LOG(Editing, "input newline case 4");
2478 ASSERT(pos.node()->isTextNode());
2481 int exceptionCode = 0;
2482 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
2483 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
2484 deleteTextFromNode(textNode, 0, pos.offset());
2485 insertNodeBefore(textBeforeNode, textNode);
2486 insertNodeBefore(nodeToInsert, textNode);
2487 Position endingPosition = Position(textNode, 0);
2489 // Handle whitespace that occurs after the split
2490 document()->updateLayout();
2491 if (!endingPosition.isRenderedCharacter()) {
2492 // Clear out all whitespace and insert one non-breaking space
2493 deleteInsignificantTextDownstream(endingPosition);
2494 insertTextIntoNode(textNode, 0, nonBreakingSpaceString());
2497 setEndingSelection(endingPosition);
2499 rebalanceWhitespace();
2502 //------------------------------------------------------------------------------------------
2503 // InsertNodeBeforeCommand
2505 InsertNodeBeforeCommand::InsertNodeBeforeCommand(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
2506 : EditCommand(document), m_insertChild(insertChild), m_refChild(refChild)
2508 ASSERT(m_insertChild);
2509 m_insertChild->ref();
2515 InsertNodeBeforeCommand::~InsertNodeBeforeCommand()
2517 ASSERT(m_insertChild);
2518 m_insertChild->deref();
2521 m_refChild->deref();
2524 void InsertNodeBeforeCommand::doApply()
2526 ASSERT(m_insertChild);
2528 ASSERT(m_refChild->parentNode());
2530 int exceptionCode = 0;
2531 m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
2532 ASSERT(exceptionCode == 0);
2535 void InsertNodeBeforeCommand::doUnapply()
2537 ASSERT(m_insertChild);
2539 ASSERT(m_refChild->parentNode());
2541 int exceptionCode = 0;
2542 m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
2543 ASSERT(exceptionCode == 0);
2546 //------------------------------------------------------------------------------------------
2547 // InsertParagraphSeparatorCommand
2549 InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(DocumentImpl *document)
2550 : CompositeEditCommand(document), m_style(0)
2554 InsertParagraphSeparatorCommand::~InsertParagraphSeparatorCommand()
2556 derefNodesInList(clonedNodes);
2561 bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
2566 ElementImpl *InsertParagraphSeparatorCommand::createParagraphElement()
2568 ElementImpl *element = createDefaultParagraphElement(document());
2570 clonedNodes.append(element);
2574 void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
2576 // FIXME: Improve typing style.
2577 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2578 CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
2579 computedStyle->ref();
2582 m_style = computedStyle->copyInheritableProperties();
2584 computedStyle->deref();
2586 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
2588 m_style->merge(typingStyle);
2591 void InsertParagraphSeparatorCommand::applyStyleAfterInsertion()
2593 // FIXME: Improve typing style.
2594 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2598 CSSComputedStyleDeclarationImpl endingStyle(endingSelection().start().node());
2599 endingStyle.diff(m_style);
2600 if (m_style->length() > 0) {
2601 applyStyle(m_style);
2605 void InsertParagraphSeparatorCommand::doApply()
2607 bool splitText = false;
2608 Selection selection = endingSelection();
2609 if (selection.isNone())
2612 // Delete the current selection.
2613 // If the selection is a range and the start and end nodes are in different blocks,
2614 // then this command bails after the delete, but takes the one additional step of
2615 // moving the selection downstream so it is in the ending block (if that block is
2616 // still around, that is).
2617 Position pos = selection.start();
2619 if (selection.isRange()) {
2620 NodeImpl *startBlockBeforeDelete = selection.start().node()->enclosingBlockFlowElement();
2621 NodeImpl *endBlockBeforeDelete = selection.end().node()->enclosingBlockFlowElement();
2622 bool doneAfterDelete = startBlockBeforeDelete != endBlockBeforeDelete;
2623 calculateStyleBeforeInsertion(pos);
2624 deleteSelection(false, false);
2625 if (doneAfterDelete) {
2626 document()->updateLayout();
2627 setEndingSelection(endingSelection().start().downstream());
2628 rebalanceWhitespace();
2629 applyStyleAfterInsertion();
2632 pos = endingSelection().start();
2635 calculateStyleBeforeInsertion(pos);
2637 // Find the start block.
2638 NodeImpl *startNode = pos.node();
2639 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
2640 if (!startBlock || !startBlock->parentNode())
2643 VisiblePosition visiblePos(pos);
2644 bool isFirstInBlock = isFirstVisiblePositionInBlock(visiblePos);
2645 bool isLastInBlock = isLastVisiblePositionInBlock(visiblePos);
2646 bool startBlockIsRoot = startBlock == startBlock->rootEditableElement();
2648 // This is the block that is going to be inserted.
2649 NodeImpl *blockToInsert = startBlockIsRoot ? createParagraphElement() : startBlock->cloneNode(false);
2651 //---------------------------------------------------------------------
2652 // Handle empty block case.
2653 if (isFirstInBlock && isLastInBlock) {
2654 LOG(Editing, "insert paragraph separator: empty block case");
2655 if (startBlockIsRoot) {
2656 NodeImpl *extraBlock = createParagraphElement();
2657 appendNode(extraBlock, startBlock);
2658 insertBlockPlaceholder(extraBlock);
2659 appendNode(blockToInsert, startBlock);
2662 insertNodeAfter(blockToInsert, startBlock);
2664 insertBlockPlaceholder(blockToInsert);
2665 setEndingSelection(Position(blockToInsert, 0));
2666 applyStyleAfterInsertion();
2670 //---------------------------------------------------------------------
2671 // Handle case when position is in the first visible position in its block.
2672 // and similar case where upstream position is in another block.
2673 bool upstreamInDifferentBlock = startBlock != pos.upstream(DoNotStayInBlock).node()->enclosingBlockFlowElement();
2674 if (upstreamInDifferentBlock || isFirstInBlock) {
2675 LOG(Editing, "insert paragraph separator: first in block case");
2676 pos = pos.downstream(StayInBlock);
2677 NodeImpl *refNode = isFirstInBlock && !startBlockIsRoot ? startBlock : pos.node();
2678 insertNodeBefore(blockToInsert, refNode);
2679 insertBlockPlaceholder(blockToInsert);
2680 setEndingSelection(Position(blockToInsert, 0));
2681 applyStyleAfterInsertion();
2682 setEndingSelection(pos);
2686 //---------------------------------------------------------------------
2687 // Handle case when position is in the last visible position in its block,
2688 // and similar case where downstream position is in another block.
2689 bool downstreamInDifferentBlock = startBlock != pos.downstream(DoNotStayInBlock).node()->enclosingBlockFlowElement();
2690 if (downstreamInDifferentBlock || isLastInBlock) {
2691 LOG(Editing, "insert paragraph separator: last in block case");
2692 NodeImpl *refNode = isLastInBlock && !startBlockIsRoot ? startBlock : pos.node();
2693 insertNodeAfter(blockToInsert, refNode);
2694 insertBlockPlaceholder(blockToInsert);
2695 setEndingSelection(Position(blockToInsert, 0));
2696 applyStyleAfterInsertion();
2700 //---------------------------------------------------------------------
2701 // Handle the (more complicated) general case,
2703 LOG(Editing, "insert paragraph separator: general case");
2705 // Check if pos.node() is a <br>. If it is, and the document is in quirks mode,
2706 // then this <br> will collapse away when we add a block after it. Add an extra <br>.
2707 if (!document()->inStrictMode()) {
2708 Position upstreamPos = pos.upstream(StayInBlock);
2709 if (upstreamPos.node()->id() == ID_BR)
2710 insertNodeAfter(createBreakElement(document()), upstreamPos.node());
2713 // Move downstream. Typing style code will take care of carrying along the
2714 // style of the upstream position.
2715 pos = pos.downstream(StayInBlock);
2716 startNode = pos.node();
2718 // Build up list of ancestors in between the start node and the start block.
2719 if (startNode != startBlock) {
2720 for (NodeImpl *n = startNode->parentNode(); n && n != startBlock; n = n->parentNode())
2721 ancestors.prepend(n);
2724 // Split at pos if in the middle of a text node.
2725 if (startNode->isTextNode()) {
2726 TextImpl *textNode = static_cast<TextImpl *>(startNode);
2727 bool atEnd = (unsigned long)pos.offset() >= textNode->length();
2728 if (pos.offset() > 0 && !atEnd) {
2729 SplitTextNodeCommand *splitCommand = new SplitTextNodeCommand(document(), textNode, pos.offset());
2730 EditCommandPtr cmd(splitCommand);
2731 applyCommandToComposite(cmd);
2732 startNode = splitCommand->node();
2733 pos = Position(startNode, 0);
2738 // Put the added block in the tree.
2739 if (startBlockIsRoot) {
2740 NodeImpl *lastSibling = pos.node();
2741 while (lastSibling->nextSibling())
2742 lastSibling = lastSibling->nextSibling();
2743 insertNodeAfter(blockToInsert, lastSibling);
2746 insertNodeAfter(blockToInsert, startBlock);
2749 // Make clones of ancestors in between the start node and the start block.
2750 NodeImpl *parent = blockToInsert;
2751 for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
2752 NodeImpl *child = it.current()->cloneNode(false); // shallow clone
2754 clonedNodes.append(child);
2755 appendNode(child, parent);
2759 // Move the start node and the siblings of the start node.
2760 if (startNode != startBlock) {
2761 NodeImpl *n = startNode;
2762 while (n && n != blockToInsert) {
2763 NodeImpl *next = n->nextSibling();
2765 appendNode(n, parent);
2770 // Move everything after the start node.
2771 NodeImpl *leftParent = ancestors.last();
2772 while (leftParent && leftParent != startBlock) {
2773 parent = parent->parentNode();
2774 NodeImpl *n = leftParent->nextSibling();
2776 NodeImpl *next = n->nextSibling();
2778 appendNode(n, parent);
2781 leftParent = leftParent->parentNode();
2784 // Handle whitespace that occurs after the split
2786 document()->updateLayout();
2787 pos = Position(startNode, 0);
2788 if (!pos.isRenderedCharacter()) {
2789 // Clear out all whitespace and insert one non-breaking space
2790 ASSERT(startNode && startNode->isTextNode());
2791 deleteInsignificantTextDownstream(pos);
2792 insertTextIntoNode(static_cast<TextImpl *>(startNode), 0, nonBreakingSpaceString());
2796 setEndingSelection(Position(blockToInsert, 0));
2797 rebalanceWhitespace();
2798 applyStyleAfterInsertion();
2801 //------------------------------------------------------------------------------------------
2802 // InsertParagraphSeparatorInQuotedContentCommand
2804 InsertParagraphSeparatorInQuotedContentCommand::InsertParagraphSeparatorInQuotedContentCommand(DocumentImpl *document)
2805 : CompositeEditCommand(document)
2809 InsertParagraphSeparatorInQuotedContentCommand::~InsertParagraphSeparatorInQuotedContentCommand()
2811 derefNodesInList(clonedNodes);
2813 m_breakNode->deref();
2816 void InsertParagraphSeparatorInQuotedContentCommand::doApply()
2818 Selection selection = endingSelection();
2819 if (selection.isNone())
2822 // Delete the current selection.
2823 Position pos = selection.start();
2824 if (selection.isRange()) {
2825 deleteSelection(false, false);
2826 pos = endingSelection().start().upstream();
2829 // Find the top-most blockquote from the start.
2830 NodeImpl *startNode = pos.node();
2831 NodeImpl *topBlockquote = 0;
2832 for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
2833 if (isMailBlockquote(n))
2836 if (!topBlockquote || !topBlockquote->parentNode())
2839 // Build up list of ancestors in between the start node and the top blockquote.
2840 if (startNode != topBlockquote) {
2841 for (NodeImpl *n = startNode->parentNode(); n && n != topBlockquote; n = n->parentNode())
2842 ancestors.prepend(n);
2845 // Insert a break after the top blockquote.
2846 m_breakNode = createBreakElement(document());
2848 insertNodeAfter(m_breakNode, topBlockquote);
2850 if (!isLastVisiblePositionInNode(VisiblePosition(pos), topBlockquote)) {
2851 // Split at pos if in the middle of a text node.
2852 if (startNode->isTextNode()) {
2853 TextImpl *textNode = static_cast<TextImpl *>(startNode);
2854 bool atEnd = (unsigned long)pos.offset() >= textNode->length();
2855 if (pos.offset() > 0 && !atEnd) {
2856 SplitTextNodeCommand *splitCommand = new SplitTextNodeCommand(document(), textNode, pos.offset());
2857 EditCommandPtr cmd(splitCommand);
2858 applyCommandToComposite(cmd);
2859 startNode = splitCommand->node();
2860 pos = Position(startNode, 0);
2863 startNode = startNode->traverseNextNode();
2867 else if (pos.offset() > 0) {
2868 startNode = startNode->traverseNextNode();
2872 // Insert a clone of the top blockquote after the break.
2873 NodeImpl *clonedBlockquote = topBlockquote->cloneNode(false);
2874 clonedBlockquote->ref();
2875 clonedNodes.append(clonedBlockquote);
2876 insertNodeAfter(clonedBlockquote, m_breakNode);
2878 // Make clones of ancestors in between the start node and the top blockquote.
2879 NodeImpl *parent = clonedBlockquote;
2880 for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
2881 NodeImpl *child = it.current()->cloneNode(false); // shallow clone
2883 clonedNodes.append(child);
2884 appendNode(child, parent);
2888 // Move the start node and the siblings of the start node.
2889 bool startIsBR = false;
2890 if (startNode != topBlockquote) {
2891 NodeImpl *n = startNode;
2892 startIsBR = n->id() == ID_BR;
2894 n = n->nextSibling();
2896 NodeImpl *next = n->nextSibling();
2898 appendNode(n, parent);
2903 // Move everything after the start node.
2904 NodeImpl *leftParent = ancestors.last();
2908 leftParent = topBlockquote;
2909 ElementImpl *b = createBreakElement(document());
2911 clonedNodes.append(b);
2912 appendNode(b, leftParent);
2915 leftParent = ancestors.last();
2916 while (leftParent && leftParent != topBlockquote) {
2917 parent = parent->parentNode();
2918 NodeImpl *n = leftParent->nextSibling();
2920 NodeImpl *next = n->nextSibling();
2922 appendNode(n, parent);
2925 leftParent = leftParent->parentNode();
2928 // Make sure the cloned block quote renders.
2929 insertBlockPlaceholderIfNeeded(clonedBlockquote);
2932 // Put the selection right before the break.
2933 setEndingSelection(Position(m_breakNode, 0));
2934 rebalanceWhitespace();
2937 //------------------------------------------------------------------------------------------
2938 // InsertTextCommand
2940 InsertTextCommand::InsertTextCommand(DocumentImpl *document)
2941 : CompositeEditCommand(document), m_charactersAdded(0)
2945 void InsertTextCommand::doApply()
2949 void InsertTextCommand::deleteCharacter()
2951 ASSERT(state() == Applied);
2953 Selection selection = endingSelection();
2955 if (!selection.start().node()->isTextNode())
2958 int exceptionCode = 0;
2959 int offset = selection.start().offset() - 1;
2960 if (offset >= selection.start().node()->caretMinOffset()) {
2961 TextImpl *textNode = static_cast<TextImpl *>(selection.start().node());
2962 textNode->deleteData(offset, 1, exceptionCode);
2963 ASSERT(exceptionCode == 0);
2964 selection = Selection(Position(textNode, offset));
2965 setEndingSelection(selection);
2966 m_charactersAdded--;
2970 Position InsertTextCommand::prepareForTextInsertion(bool adjustDownstream)
2972 // Prepare for text input by looking at the current position.
2973 // It may be necessary to insert a text node to receive characters.
2974 Selection selection = endingSelection();
2975 ASSERT(selection.isCaret());
2977 Position pos = selection.start();
2978 if (adjustDownstream)
2979 pos = pos.downstream(StayInBlock);
2981 pos = pos.upstream(StayInBlock);
2983 if (!pos.node()->isTextNode()) {
2984 NodeImpl *textNode = document()->createEditingTextNode("");
2985 NodeImpl *nodeToInsert = textNode;
2987 // Handle the case where there is a typing style.
2988 // FIXME: Improve typing style.
2989 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2990 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
2991 if (typingStyle && typingStyle->length() > 0)
2992 nodeToInsert = applyTypingStyle(textNode);
2994 // Now insert the node in the right place
2995 if (pos.node()->isEditableBlock()) {
2996 LOG(Editing, "prepareForTextInsertion case 1");
2997 appendNode(nodeToInsert, pos.node());
2999 else if (pos.node()->caretMinOffset() == pos.offset()) {
3000 LOG(Editing, "prepareForTextInsertion case 2");
3001 insertNodeBefore(nodeToInsert, pos.node());
3003 else if (pos.node()->caretMaxOffset() == pos.offset()) {
3004 LOG(Editing, "prepareForTextInsertion case 3");
3005 insertNodeAfter(nodeToInsert, pos.node());
3008 ASSERT_NOT_REACHED();
3010 pos = Position(textNode, 0);
3013 // Handle the case where there is a typing style.
3014 // FIXME: Improve typing style.
3015 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3016 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
3017 if (typingStyle && typingStyle->length() > 0) {
3018 if (pos.node()->isTextNode() && pos.offset() > pos.node()->caretMinOffset() && pos.offset() < pos.node()->caretMaxOffset()) {
3019 // Need to split current text node in order to insert a span.
3020 TextImpl *text = static_cast<TextImpl *>(pos.node());
3021 SplitTextNodeCommand *impl = new SplitTextNodeCommand(document(), text, pos.offset());
3022 EditCommandPtr cmd(impl);
3023 applyCommandToComposite(cmd);
3024 setEndingSelection(Position(impl->node(), 0));
3027 TextImpl *editingTextNode = document()->createEditingTextNode("");
3028 NodeImpl *node = endingSelection().start().upstream(StayInBlock).node();
3029 if (node->isBlockFlow())
3030 insertNodeAt(applyTypingStyle(editingTextNode), node, 0);
3032 insertNodeAfter(applyTypingStyle(editingTextNode), node);
3033 pos = Position(editingTextNode, 0);
3039 void InsertTextCommand::input(const DOMString &text, bool selectInsertedText)
3041 Selection selection = endingSelection();
3042 bool adjustDownstream = isFirstVisiblePositionOnLine(VisiblePosition(selection.start().downstream(StayInBlock)));
3044 // Delete the current selection, or collapse whitespace, as needed
3045 if (selection.isRange())
3048 // Delete any insignificant text that could get in the way of whitespace turning
3049 // out correctly after the insertion.
3050 deleteInsignificantTextDownstream(endingSelection().end().trailingWhitespacePosition());
3052 // Make sure the document is set up to receive text
3053 Position pos = prepareForTextInsertion(adjustDownstream);
3055 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
3056 long offset = pos.offset();
3058 // Now that we are about to add content, check to see if a placeholder element
3060 removeBlockPlaceholderIfNeeded(textNode->enclosingBlockFlowElement());
3062 // These are temporary implementations for inserting adjoining spaces
3063 // into a document. We are working on a CSS-related whitespace solution
3064 // that will replace this some day. We hope.
3066 // Treat a tab like a number of spaces. This seems to be the HTML editing convention,
3067 // although the number of spaces varies (we choose four spaces).
3068 // Note that there is no attempt to make this work like a real tab stop, it is merely
3069 // a set number of spaces. This also seems to be the HTML editing convention.
3070 for (int i = 0; i < spacesPerTab; i++) {
3071 insertSpace(textNode, offset);
3072 rebalanceWhitespace();
3073 document()->updateLayout();
3075 if (selectInsertedText)
3076 setEndingSelection(Selection(Position(textNode, offset), Position(textNode, offset + spacesPerTab)));
3078 setEndingSelection(Position(textNode, offset + spacesPerTab));
3079 m_charactersAdded += spacesPerTab;
3081 else if (isWS(text)) {
3082 insertSpace(textNode, offset);
3083 if (selectInsertedText)
3084 setEndingSelection(Selection(Position(textNode, offset), Position(textNode, offset + 1)));
3086 setEndingSelection(Position(textNode, offset + 1));
3087 m_charactersAdded++;
3088 rebalanceWhitespace();
3091 const DOMString &existingText = textNode->data();
3092 if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isWS(existingText[offset - 2])) {
3093 // DOM looks like this:
3094 // character nbsp caret
3095 // As we are about to insert a non-whitespace character at the caret
3096 // convert the nbsp to a regular space.
3097 // EDIT FIXME: This needs to be improved some day to convert back only
3098 // those nbsp's added by the editor to make rendering come out right.
3099 replaceTextInNode(textNode, offset - 1, 1, " ");
3101 insertTextIntoNode(textNode, offset, text);
3102 if (selectInsertedText)
3103 setEndingSelection(Selection(Position(textNode, offset), Position(textNode, offset + text.length())));
3105 setEndingSelection(Position(textNode, offset + text.length()));
3106 m_charactersAdded += text.length();
3110 void InsertTextCommand::insertSpace(TextImpl *textNode, unsigned long offset)
3114 DOMString text(textNode->data());
3116 // count up all spaces and newlines in front of the caret
3117 // delete all collapsed ones
3118 // this will work out OK since the offset we have been passed has been upstream-ized
3120 for (unsigned int i = offset; i < text.length(); i++) {
3127 // By checking the character at the downstream position, we can
3128 // check if there is a rendered WS at the caret
3129 Position pos(textNode, offset);
3130 Position downstream = pos.downstream();
3131 if (downstream.offset() < (long)text.length() && isWS(text[downstream.offset()]))
3132 count--; // leave this WS in
3134 deleteTextFromNode(textNode, offset, count);
3137 if (offset > 0 && offset <= text.length() - 1 && !isWS(text[offset]) && !isWS(text[offset - 1])) {
3138 // insert a "regular" space
3139 insertTextIntoNode(textNode, offset, " ");
3143 if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
3144 // DOM looks like this:
3146 // insert a space between the two nbsps
3147 insertTextIntoNode(textNode, offset - 1, " ");
3152 insertTextIntoNode(textNode, offset, nonBreakingSpaceString());
3155 bool InsertTextCommand::isInsertTextCommand() const
3160 //------------------------------------------------------------------------------------------
3161 // JoinTextNodesCommand
3163 JoinTextNodesCommand::JoinTextNodesCommand(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
3164 : EditCommand(document), m_text1(text1), m_text2(text2)
3168 ASSERT(m_text1->nextSibling() == m_text2);
3169 ASSERT(m_text1->length() > 0);
3170 ASSERT(m_text2->length() > 0);
3176 JoinTextNodesCommand::~JoinTextNodesCommand()
3184 void JoinTextNodesCommand::doApply()
3188 ASSERT(m_text1->nextSibling() == m_text2);
3190 int exceptionCode = 0;
3191 m_text2->insertData(0, m_text1->data(), exceptionCode);
3192 ASSERT(exceptionCode == 0);
3194 m_text2->parentNode()->removeChild(m_text1, exceptionCode);
3195 ASSERT(exceptionCode == 0);
3197 m_offset = m_text1->length();
3200 void JoinTextNodesCommand::doUnapply()
3203 ASSERT(m_offset > 0);
3205 int exceptionCode = 0;
3207 m_text2->deleteData(0, m_offset, exceptionCode);
3208 ASSERT(exceptionCode == 0);
3210 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
3211 ASSERT(exceptionCode == 0);
3213 ASSERT(m_text2->previousSibling()->isTextNode());
3214 ASSERT(m_text2->previousSibling() == m_text1);
3217 //------------------------------------------------------------------------------------------
3218 // MoveSelectionCommand
3220 MoveSelectionCommand::MoveSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, Position &position, bool smartMove)
3221 : CompositeEditCommand(document), m_fragment(fragment), m_position(position), m_smartMove(smartMove)
3227 MoveSelectionCommand::~MoveSelectionCommand()
3230 m_fragment->deref();
3233 void MoveSelectionCommand::doApply()
3235 Selection selection = endingSelection();
3236 ASSERT(selection.isRange());
3238 Position pos = m_position;
3240 // Update the position otherwise it may become invalid after the selection is deleted.
3241 NodeImpl *positionNode = m_position.node();
3242 long positionOffset = m_position.offset();
3243 Position selectionEnd = selection.end();
3244 long selectionEndOffset = selectionEnd.offset();
3245 if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) {
3246 positionOffset -= selectionEndOffset;
3247 Position selectionStart = selection.start();
3248 if (selectionStart.node() == positionNode) {
3249 positionOffset += selectionStart.offset();
3251 pos = Position(positionNode, positionOffset);
3254 deleteSelection(m_smartMove);
3256 // If the node for the destination has been removed as a result of the deletion,
3257 // set the destination to the ending point after the deletion.
3258 // Fixes: <rdar://problem/3910425> REGRESSION (Mail): Crash in ReplaceSelectionCommand;
3259 // selection is empty, leading to null deref
3260 if (!pos.node()->inDocument())
3261 pos = endingSelection().start();
3263 setEndingSelection(pos);
3264 EditCommandPtr cmd(new ReplaceSelectionCommand(document(), m_fragment, true, m_smartMove));
3265 applyCommandToComposite(cmd);
3268 EditAction MoveSelectionCommand::editingAction() const
3270 return EditActionDrag;
3273 //------------------------------------------------------------------------------------------
3274 // RebalanceWhitespaceCommand
3276 RebalanceWhitespaceCommand::RebalanceWhitespaceCommand(DocumentImpl *document, const Position &pos)
3277 : EditCommand(document), m_position(pos), m_upstreamOffset(InvalidOffset), m_downstreamOffset(InvalidOffset)
3281 RebalanceWhitespaceCommand::~RebalanceWhitespaceCommand()
3285 void RebalanceWhitespaceCommand::doApply()
3287 static DOMString space(" ");
3289 if (m_position.isNull() || !m_position.node()->isTextNode())
3292 TextImpl *textNode = static_cast<TextImpl *>(m_position.node());
3293 DOMString text = textNode->data();
3294 if (text.length() == 0)
3297 // find upstream offset
3298 long upstream = m_position.offset();
3299 while (upstream > 0 && isWS(text[upstream - 1]) || isNBSP(text[upstream - 1])) {
3301 m_upstreamOffset = upstream;
3304 // find downstream offset
3305 long downstream = m_position.offset();
3306 while ((unsigned)downstream < text.length() && isWS(text[downstream]) || isNBSP(text[downstream])) {
3308 m_downstreamOffset = downstream;
3311 if (m_upstreamOffset == InvalidOffset && m_downstreamOffset == InvalidOffset)
3314 m_upstreamOffset = upstream;
3315 m_downstreamOffset = downstream;
3316 long length = m_downstreamOffset - m_upstreamOffset;
3318 m_beforeString = text.substring(m_upstreamOffset, length);
3320 // The following loop figures out a "rebalanced" whitespace string for any length
3321 // string, and takes into account the special cases that need to handled for the
3322 // start and end of strings (i.e. first and last character must be an nbsp.
3323 long i = m_upstreamOffset;
3324 while (i < m_downstreamOffset) {
3325 long add = (m_downstreamOffset - i) % 3;
3328 m_afterString += nonBreakingSpaceString();
3329 m_afterString += space;
3330 m_afterString += nonBreakingSpaceString();
3334 if (i == 0 || (unsigned)i + 1 == text.length()) // at start or end of string
3335 m_afterString += nonBreakingSpaceString();
3337 m_afterString += space;
3340 if ((unsigned)i + 2 == text.length()) {
3342 m_afterString += nonBreakingSpaceString();
3343 m_afterString += nonBreakingSpaceString();
3346 m_afterString += nonBreakingSpaceString();
3347 m_afterString += space;
3354 text.remove(m_upstreamOffset, length);
3355 text.insert(m_afterString, m_upstreamOffset);
3358 void RebalanceWhitespaceCommand::doUnapply()
3360 if (m_upstreamOffset == InvalidOffset && m_downstreamOffset == InvalidOffset)
3363 ASSERT(m_position.node()->isTextNode());
3364 TextImpl *textNode = static_cast<TextImpl *>(m_position.node());
3365 DOMString text = textNode->data();
3366 text.remove(m_upstreamOffset, m_afterString.length());
3367 text.insert(m_beforeString, m_upstreamOffset);
3370 bool RebalanceWhitespaceCommand::preservesTypingStyle() const
3375 //------------------------------------------------------------------------------------------
3376 // RemoveCSSPropertyCommand
3378 RemoveCSSPropertyCommand::RemoveCSSPropertyCommand(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property)
3379 : EditCommand(document), m_decl(decl->makeMutable()), m_property(property), m_important(false)
3385 RemoveCSSPropertyCommand::~RemoveCSSPropertyCommand()
3391 void RemoveCSSPropertyCommand::doApply()
3395 m_oldValue = m_decl->getPropertyValue(m_property);
3396 ASSERT(!m_oldValue.isNull());
3398 m_important = m_decl->getPropertyPriority(m_property);
3399 m_decl->removeProperty(m_property);
3402 void RemoveCSSPropertyCommand::doUnapply()
3405 ASSERT(!m_oldValue.isNull());
3407 m_decl->setProperty(m_property, m_oldValue, m_important);
3410 //------------------------------------------------------------------------------------------
3411 // RemoveNodeAttributeCommand
3413 RemoveNodeAttributeCommand::RemoveNodeAttributeCommand(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute)
3414 : EditCommand(document), m_element(element), m_attribute(attribute)
3420 RemoveNodeAttributeCommand::~RemoveNodeAttributeCommand()
3426 void RemoveNodeAttributeCommand::doApply()
3430 m_oldValue = m_element->getAttribute(m_attribute);
3431 ASSERT(!m_oldValue.isNull());
3433 int exceptionCode = 0;
3434 m_element->removeAttribute(m_attribute, exceptionCode);
3435 ASSERT(exceptionCode == 0);
3438 void RemoveNodeAttributeCommand::doUnapply()
3441 ASSERT(!m_oldValue.isNull());
3443 int exceptionCode = 0;
3444 m_element->setAttribute(m_attribute, m_oldValue.implementation(), exceptionCode);
3445 ASSERT(exceptionCode == 0);
3448 //------------------------------------------------------------------------------------------
3449 // RemoveNodeCommand
3451 RemoveNodeCommand::RemoveNodeCommand(DocumentImpl *document, NodeImpl *removeChild)
3452 : EditCommand(document), m_parent(0), m_removeChild(removeChild), m_refChild(0)
3454 ASSERT(m_removeChild);
3455 m_removeChild->ref();
3457 m_parent = m_removeChild->parentNode();
3461 m_refChild = m_removeChild->nextSibling();
3466 RemoveNodeCommand::~RemoveNodeCommand()
3471 ASSERT(m_removeChild);
3472 m_removeChild->deref();
3475 m_refChild->deref();
3478 void RemoveNodeCommand::doApply()
3481 ASSERT(m_removeChild);
3483 int exceptionCode = 0;
3484 m_parent->removeChild(m_removeChild, exceptionCode);
3485 ASSERT(exceptionCode == 0);
3488 void RemoveNodeCommand::doUnapply()
3491 ASSERT(m_removeChild);
3493 int exceptionCode = 0;
3494 m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
3495 ASSERT(exceptionCode == 0);
3498 //------------------------------------------------------------------------------------------
3499 // RemoveNodePreservingChildrenCommand
3501 RemoveNodePreservingChildrenCommand::RemoveNodePreservingChildrenCommand(DocumentImpl *document, NodeImpl *node)
3502 : CompositeEditCommand(document), m_node(node)
3508 RemoveNodePreservingChildrenCommand::~RemoveNodePreservingChildrenCommand()
3514 void RemoveNodePreservingChildrenCommand::doApply()
3516 while (NodeImpl* curr = node()->firstChild()) {
3518 insertNodeBefore(curr, node());
3523 //------------------------------------------------------------------------------------------
3524 // ReplaceSelectionCommand
3526 ReplacementFragment::ReplacementFragment(DocumentFragmentImpl *fragment)
3527 : m_fragment(fragment), m_hasInterchangeNewline(false), m_hasMoreThanOneBlock(false)
3530 m_type = EmptyFragment;
3536 NodeImpl *firstChild = m_fragment->firstChild();
3537 NodeImpl *lastChild = m_fragment->lastChild();
3540 m_type = EmptyFragment;
3544 if (firstChild == lastChild && firstChild->isTextNode()) {
3545 m_type = SingleTextNodeFragment;
3549 m_type = TreeFragment;
3551 NodeImpl *node = firstChild;
3552 int realBlockCount = 0;
3553 NodeImpl *nodeToDelete = 0;
3555 NodeImpl *next = node->traverseNextNode();
3556 if (isInterchangeNewlineNode(node)) {
3557 m_hasInterchangeNewline = true;
3558 nodeToDelete = node;
3560 else if (isInterchangeConvertedSpaceSpan(node)) {
3562 while ((n = node->firstChild())) {
3565 insertNodeBefore(n, node);
3570 next = n->traverseNextNode();
3572 else if (isProbablyBlock(node))
3578 removeNode(nodeToDelete);
3580 int blockCount = realBlockCount;
3581 firstChild = m_fragment->firstChild();
3582 lastChild = m_fragment->lastChild();
3583 if (!isProbablyBlock(firstChild))
3585 if (!isProbablyBlock(lastChild) && realBlockCount > 0)
3589 m_hasMoreThanOneBlock = true;
3592 ReplacementFragment::~ReplacementFragment()
3595 m_fragment->deref();
3598 NodeImpl *ReplacementFragment::firstChild() const
3600 return m_fragment->firstChild();
3603 NodeImpl *ReplacementFragment::lastChild() const
3605 return m_fragment->lastChild();
3608 NodeImpl *ReplacementFragment::mergeStartNode() const
3610 NodeImpl *node = m_fragment->firstChild();
3613 if (!isProbablyBlock(node))
3615 return node->firstChild();
3618 NodeImpl *ReplacementFragment::mergeEndNode() const
3620 NodeImpl *node = m_fragment->lastChild();
3621 while (node && node->lastChild())
3622 node = node->lastChild();
3624 if (isProbablyBlock(node))
3627 NodeImpl *startingBlock = enclosingBlock(node);
3628 ASSERT(startingBlock != node);
3630 NodeImpl *prev = node->traversePreviousNode();
3631 if (prev == m_fragment || prev == startingBlock || enclosingBlock(prev) != startingBlock)
3639 void ReplacementFragment::pruneEmptyNodes()
3644 NodeImpl *node = m_fragment->firstChild();
3646 if ((node->isTextNode() && static_cast<TextImpl *>(node)->length() == 0) ||
3647 (isProbablyBlock(node) && node->childNodeCount() == 0)) {
3648 NodeImpl *next = node->traverseNextSibling();
3654 node = node->traverseNextNode();
3660 bool ReplacementFragment::isInterchangeNewlineNode(const NodeImpl *node)
3662 static DOMString interchangeNewlineClassString(AppleInterchangeNewline);
3663 return node && node->id() == ID_BR && static_cast<const ElementImpl *>(node)->getAttribute(ATTR_CLASS) == interchangeNewlineClassString;
3666 bool ReplacementFragment::isInterchangeConvertedSpaceSpan(const NodeImpl *node)
3668 static DOMString convertedSpaceSpanClassString(AppleConvertedSpace);
3669 return node->isHTMLElement() && static_cast<const HTMLElementImpl *>(node)->getAttribute(ATTR_CLASS) == convertedSpaceSpanClassString;
3672 NodeImpl *ReplacementFragment::enclosingBlock(NodeImpl *node) const
3674 while (node && !isProbablyBlock(node))
3675 node = node->parentNode();
3676 return node ? node : m_fragment;
3679 void ReplacementFragment::removeNode(NodeImpl *node)
3684 NodeImpl *parent = node->parentNode();
3688 int exceptionCode = 0;
3689 parent->removeChild(node, exceptionCode);
3690 ASSERT(exceptionCode == 0);
3693 void ReplacementFragment::insertNodeBefore(NodeImpl *node, NodeImpl *refNode)
3695 if (!node || !refNode)
3698 NodeImpl *parent = refNode->parentNode();
3702 int exceptionCode = 0;
3703 parent->insertBefore(node, refNode, exceptionCode);
3704 ASSERT(exceptionCode == 0);
3708 bool isProbablyBlock(const NodeImpl *node)
3713 switch (node->id()) {
3739 ReplaceSelectionCommand::ReplaceSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, bool selectReplacement, bool smartReplace)
3740 : CompositeEditCommand(document),
3741 m_fragment(fragment),
3742 m_selectReplacement(selectReplacement),
3743 m_smartReplace(smartReplace)
3747 ReplaceSelectionCommand::~ReplaceSelectionCommand()
3751 void ReplaceSelectionCommand::doApply()
3753 Selection selection = endingSelection();
3754 VisiblePosition visibleStart(selection.start());
3755 VisiblePosition visibleEnd(selection.end());
3756 bool startAtStartOfBlock = isFirstVisiblePositionInBlock(visibleStart);
3757 bool startAtEndOfBlock = isLastVisiblePositionInBlock(visibleStart);
3758 bool startAtBlockBoundary = startAtStartOfBlock || startAtEndOfBlock;
3759 NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement();
3760 NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement();
3761 bool mergeStart = false;
3762 bool mergeEnd = false;
3763 if (startBlock == startBlock->rootEditableElement() && startAtStartOfBlock && startAtEndOfBlock) {
3764 // Empty document. Merge neither start nor end.
3765 mergeStart = mergeEnd = false;
3768 mergeStart = !isStartOfParagraph(visibleStart);
3769 mergeEnd = !m_fragment.hasInterchangeNewline() && m_fragment.hasMoreThanOneBlock() && !isEndOfParagraph(visibleEnd);
3772 Position startPos = Position(selection.start().node()->enclosingBlockFlowElement(), 0);
3774 EStayInBlock upstreamStayInBlock = StayInBlock;
3776 // Delete the current selection, or collapse whitespace, as needed
3777 if (selection.isRange()) {
3778 deleteSelection(false, !(m_fragment.hasInterchangeNewline() || m_fragment.hasMoreThanOneBlock()));
3780 else if (selection.isCaret() && mergeEnd && !startAtBlockBoundary) {
3781 // The start and the end need to wind up in separate blocks.
3782 // Insert a paragraph separator to make that happen.
3783 insertParagraphSeparator();
3784 upstreamStayInBlock = DoNotStayInBlock;
3787 selection = endingSelection();
3788 if (startAtStartOfBlock && startBlock->inDocument())
3789 startPos = Position(startBlock, 0);
3790 else if (startAtEndOfBlock)
3791 startPos = selection.start().downstream(StayInBlock);
3793 startPos = selection.start().upstream(upstreamStayInBlock);
3794 endPos = selection.end().downstream();
3796 // This command does not use any typing style that is set as a residual effect of
3798 // FIXME: Improve typing style.
3799 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3800 KHTMLPart *part = document()->part();
3801 part->clearTypingStyle();
3804 if (!m_fragment.firstChild())
3807 // Now that we are about to add content, check to see if a placeholder element
3809 NodeImpl *block = startPos.node()->enclosingBlockFlowElement();
3810 NodeImpl *placeholderBlock = 0;
3811 if (removeBlockPlaceholderIfNeeded(block)) {
3812 placeholderBlock = block;
3813 Position pos = Position(block, 0);
3814 if (!endPos.node()->inDocument()) // endPos might have been in the placeholder just removed.
3819 bool addLeadingSpace = false;
3820 bool addTrailingSpace = false;
3821 if (m_smartReplace) {
3822 addLeadingSpace = startPos.leadingWhitespacePosition().isNotNull();
3823 if (addLeadingSpace) {
3824 QChar previousChar = VisiblePosition(startPos).previous().character();
3825 if (!previousChar.isNull()) {
3826 addLeadingSpace = !part->isCharacterSmartReplaceExempt(previousChar, true);
3829 addTrailingSpace = endPos.trailingWhitespacePosition().isNotNull();
3830 if (addTrailingSpace) {
3831 QChar thisChar = VisiblePosition(endPos).character();
3832 if (!thisChar.isNull()) {
3833 addTrailingSpace = !part->isCharacterSmartReplaceExempt(thisChar, false);
3838 document()->updateLayout();
3840 Position insertionPos = startPos;
3841 NodeImpl *firstNodeInserted = 0;
3842 NodeImpl *lastNodeInserted = 0;
3843 bool lastNodeInsertedInMergeEnd = false;
3845 // prune empty nodes from fragment
3846 m_fragment.pruneEmptyNodes();
3848 // Merge content into the end block, if necessary.
3850 NodeImpl *node = m_fragment.mergeEndNode();
3852 NodeImpl *refNode = node;
3853 NodeImpl *node = refNode ? refNode->nextSibling() : 0;
3854 insertNodeAt(refNode, endPos.node(), endPos.offset());
3855 firstNodeInserted = refNode;
3856 lastNodeInserted = refNode;
3857 while (node && !isProbablyBlock(node)) {
3858 NodeImpl *next = node->nextSibling();
3859 insertNodeAfter(node, refNode);
3860 lastNodeInserted = node;
3864 lastNodeInsertedInMergeEnd = true;
3868 // prune empty nodes from fragment
3869 m_fragment.pruneEmptyNodes();
3871 // Merge content into the start block, if necessary.
3873 NodeImpl *node = m_fragment.mergeStartNode();
3874 NodeImpl *insertionNode = 0;
3876 NodeImpl *refNode = node;
3877 NodeImpl *node = refNode ? refNode->nextSibling() : 0;
3878 insertNodeAt(refNode, startPos.node(), startPos.offset());
3879 firstNodeInserted = refNode;
3880 if (!lastNodeInsertedInMergeEnd)
3881 lastNodeInserted = refNode;
3882 insertionNode = refNode;
3883 while (node && !isProbablyBlock(node)) {
3884 NodeImpl *next = node->nextSibling();
3885 insertNodeAfter(node, refNode);
3886 if (!lastNodeInsertedInMergeEnd)
3887 lastNodeInserted = node;
3888 insertionNode = node;
3893 if (insertionNode) {
3894 if (insertionNode->isTextNode())
3895 insertionPos = Position(insertionNode, insertionNode->caretMaxOffset());
3896 else if (insertionNode->childNodeCount() > 0)
3897 insertionPos = Position(insertionNode, insertionNode->childNodeCount());
3899 insertionPos = Position(insertionNode->parentNode(), insertionNode->nodeIndex() + 1);
3903 // prune empty nodes from fragment
3904 m_fragment.pruneEmptyNodes();
3906 // Merge everything remaining.
3907 NodeImpl *node = m_fragment.firstChild();
3909 NodeImpl *refNode = node;
3910 NodeImpl *node = refNode ? refNode->nextSibling() : 0;
3911 VisiblePosition visiblePos(insertionPos);
3912 bool insertionNodeIsBody = insertionPos.node()->id() == ID_BODY;
3913 if (!mergeStart && !insertionNodeIsBody && isProbablyBlock(refNode) && isStartOfParagraph(visiblePos)) {
3914 Position pos = insertionPos;
3915 if (!insertionPos.node()->isTextNode() && !insertionPos.node()->isBlockFlow() && insertionPos.offset() > 0)
3916 pos = insertionPos.downstream(StayInBlock);
3917 insertNodeBefore(refNode, pos.node());
3919 else if (!mergeEnd && !insertionNodeIsBody && isProbablyBlock(refNode) && isEndOfParagraph(visiblePos))
3920 insertNodeAfter(refNode, insertionPos.node());
3922 insertNodeAt(refNode, insertionPos.node(), insertionPos.offset());
3923 if (!firstNodeInserted)
3924 firstNodeInserted = refNode;
3925 if (!lastNodeInsertedInMergeEnd)
3926 lastNodeInserted = refNode;
3928 NodeImpl *next = node->nextSibling();
3929 insertNodeAfter(node, refNode);
3930 if (!lastNodeInsertedInMergeEnd)
3931 lastNodeInserted = node;
3935 document()->updateLayout();
3936 insertionPos = Position(lastNodeInserted, lastNodeInserted->caretMaxOffset());
3939 // Handle "smart replace" whitespace
3940 if (addTrailingSpace && lastNodeInserted) {
3941 if (lastNodeInserted->isTextNode()) {
3942 TextImpl *text = static_cast<TextImpl *>(lastNodeInserted);
3943 insertTextIntoNode(text, text->length(), nonBreakingSpaceString());
3944 insertionPos = Position(text, text->length());
3947 NodeImpl *node = document()->createEditingTextNode(nonBreakingSpaceString());
3948 insertNodeAfter(node, lastNodeInserted);
3949 if (!firstNodeInserted)
3950 firstNodeInserted = node;
3951 lastNodeInserted = node;
3952 insertionPos = Position(node, 1);
3956 if (addLeadingSpace && firstNodeInserted) {
3957 if (firstNodeInserted->isTextNode()) {
3958 TextImpl *text = static_cast<TextImpl *>(firstNodeInserted);
3959 insertTextIntoNode(text, 0, nonBreakingSpaceString());
3962 NodeImpl *node = document()->createEditingTextNode(nonBreakingSpaceString());
3963 insertNodeBefore(node, firstNodeInserted);
3964 firstNodeInserted = node;
3965 if (!lastNodeInsertedInMergeEnd)
3966 lastNodeInserted = node;
3970 // Handle trailing newline
3971 if (m_fragment.hasInterchangeNewline()) {
3972 if ((startBlock == endBlock) && (VisiblePosition(lastNodeInserted, lastNodeInserted->caretMaxOffset()).next().isNull())) {
3974 setEndingSelection(insertionPos);
3975 insertParagraphSeparator();
3976 endPos = endingSelection().end().downstream();
3978 completeHTMLReplacement(startPos, endPos);
3981 if (lastNodeInserted->id() == ID_BR && !document()->inStrictMode()) {
3982 document()->updateLayout();
3983 VisiblePosition pos(Position(lastNodeInserted, 0));
3984 if (isLastVisiblePositionInBlock(pos)) {
3985 NodeImpl *next = lastNodeInserted->traverseNextNode();
3986 bool hasTrailingBR = next && next->id() == ID_BR && lastNodeInserted->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
3987 if (!hasTrailingBR) {
3988 // Insert an "extra" BR at the end of the block.
3989 insertNodeBefore(createBreakElement(document()), lastNodeInserted);
3993 completeHTMLReplacement(firstNodeInserted, lastNodeInserted);
3996 if (placeholderBlock) {
3997 document()->updateLayout();
3998 if (!placeholderBlock->renderer() || placeholderBlock->renderer()->height() == 0)
3999 removeNode(placeholderBlock);
4003 void ReplaceSelectionCommand::completeHTMLReplacement(const Position &start, const Position &end)
4005 if (start.isNull() || !start.node()->inDocument() || end.isNull() || !end.node()->inDocument())
4007 m_selectReplacement ? setEndingSelection(Selection(start, end)) : setEndingSelection(end);
4008 rebalanceWhitespace();
4011 void ReplaceSelectionCommand::completeHTMLReplacement(NodeImpl *firstNodeInserted, NodeImpl *lastNodeInserted)
4013 if (!firstNodeInserted || !firstNodeInserted->inDocument() ||
4014 !lastNodeInserted || !lastNodeInserted->inDocument())
4017 // Find the last leaf.
4018 NodeImpl *lastLeaf = lastNodeInserted;
4020 NodeImpl *nextChild = lastLeaf->lastChild();
4023 lastLeaf = nextChild;
4026 // Find the first leaf.
4027 NodeImpl *firstLeaf = firstNodeInserted;
4029 NodeImpl *nextChild = firstLeaf->firstChild();
4032 firstLeaf = nextChild;
4035 Position start(firstLeaf, firstLeaf->caretMinOffset());
4036 Position end(lastLeaf, lastLeaf->caretMaxOffset());
4037 Selection replacementSelection(start, end);
4038 if (m_selectReplacement) {
4039 // Select what was inserted.
4040 setEndingSelection(replacementSelection);
4043 // Place the cursor after what was inserted, and mark misspellings in the inserted content.
4044 setEndingSelection(end);
4046 rebalanceWhitespace();
4049 //------------------------------------------------------------------------------------------
4050 // SetNodeAttributeCommand
4052 SetNodeAttributeCommand::SetNodeAttributeCommand(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value)
4053 : EditCommand(document), m_element(element), m_attribute(attribute), m_value(value)
4057 ASSERT(!m_value.isNull());
4060 SetNodeAttributeCommand::~SetNodeAttributeCommand()
4066 void SetNodeAttributeCommand::doApply()
4069 ASSERT(!m_value.isNull());
4071 int exceptionCode = 0;
4072 m_oldValue = m_element->getAttribute(m_attribute);
4073 m_element->setAttribute(m_attribute, m_value.implementation(), exceptionCode);
4074 ASSERT(exceptionCode == 0);
4077 void SetNodeAttributeCommand::doUnapply()
4081 int exceptionCode = 0;
4082 if (m_oldValue.isNull())
4083 m_element->removeAttribute(m_attribute, exceptionCode);
4085 m_element->setAttribute(m_attribute, m_oldValue.implementation(), exceptionCode);
4086 ASSERT(exceptionCode == 0);
4089 //------------------------------------------------------------------------------------------
4090 // SplitTextNodeCommand
4092 SplitTextNodeCommand::SplitTextNodeCommand(DocumentImpl *document, TextImpl *text, long offset)
4093 : EditCommand(document), m_text1(0), m_text2(text), m_offset(offset)
4096 ASSERT(m_text2->length() > 0);
4101 SplitTextNodeCommand::~SplitTextNodeCommand()