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 "cssparser.h"
32 #include "cssproperties.h"
34 #include "dom_docimpl.h"
35 #include "dom_docimpl.h"
36 #include "dom_elementimpl.h"
37 #include "dom_nodeimpl.h"
38 #include "dom_position.h"
39 #include "dom_positioniterator.h"
40 #include "dom_stringimpl.h"
41 #include "dom_textimpl.h"
42 #include "dom2_rangeimpl.h"
43 #include "html_elementimpl.h"
44 #include "html_imageimpl.h"
45 #include "html_interchange.h"
46 #include "htmlattrs.h"
48 #include "khtml_part.h"
49 #include "khtml_part.h"
50 #include "khtmlview.h"
53 #include "render_object.h"
54 #include "render_style.h"
55 #include "render_text.h"
56 #include "visible_position.h"
57 #include "visible_units.h"
60 using DOM::CSSComputedStyleDeclarationImpl;
61 using DOM::CSSMutableStyleDeclarationImpl;
63 using DOM::CSSPrimitiveValue;
64 using DOM::CSSPrimitiveValueImpl;
65 using DOM::CSSProperty;
66 using DOM::CSSStyleDeclarationImpl;
68 using DOM::CSSValueImpl;
69 using DOM::DocumentFragmentImpl;
70 using DOM::DocumentImpl;
72 using DOM::DOMStringImpl;
73 using DOM::DoNotStayInBlock;
74 using DOM::DoNotUpdateLayout;
75 using DOM::EditingTextImpl;
76 using DOM::ElementImpl;
77 using DOM::EStayInBlock;
78 using DOM::HTMLElementImpl;
79 using DOM::HTMLImageElementImpl;
80 using DOM::NamedAttrMapImpl;
83 using DOM::NodeListImpl;
85 using DOM::PositionIterator;
88 using DOM::StayInBlock;
90 using DOM::TreeWalkerImpl;
93 #include "KWQAssertions.h"
94 #include "KWQLogging.h"
95 #include "KWQKHTMLPart.h"
99 #define ASSERT(assertion) ((void)0)
100 #define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
101 #define ASSERT_NOT_REACHED() ((void)0)
102 #define LOG(channel, formatAndArgs...) ((void)0)
103 #define ERROR(formatAndArgs...) ((void)0)
104 #define ASSERT(assertion) assert(assertion)
106 #define debugPosition(a,b) ((void)0)
107 #define debugNode(a,b) ((void)0)
111 #define IF_IMPL_NULL_RETURN_ARG(arg) do { \
112 if (isNull()) { return arg; } \
115 #define IF_IMPL_NULL_RETURN do { \
116 if (isNull()) { return; } \
121 static inline bool isNBSP(const QChar &c)
123 return c == QChar(0xa0);
126 static inline bool isWS(const QChar &c)
128 return c.isSpace() && c != QChar(0xa0);
131 static inline bool isWS(const DOMString &text)
133 if (text.length() != 1)
136 return isWS(text[0]);
139 static inline bool isWS(const Position &pos)
144 if (!pos.node()->isTextNode())
147 const DOMString &string = static_cast<TextImpl *>(pos.node())->data();
148 return isWS(string[pos.offset()]);
151 static const int spacesPerTab = 4;
153 static inline bool isTab(const DOMString &text)
155 static QChar tabCharacter = QChar(0x9);
156 if (text.length() != 1)
159 return text[0] == tabCharacter;
162 static inline bool isTableStructureNode(const NodeImpl *node)
164 RenderObject *r = node->renderer();
165 return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol()));
168 static DOMString &nonBreakingSpaceString()
170 static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
171 return nonBreakingSpaceString;
174 static DOMString &styleSpanClassString()
176 static DOMString styleSpanClassString = AppleStyleSpanClass;
177 return styleSpanClassString;
180 static bool isEmptyStyleSpan(const NodeImpl *node)
182 if (!node || !node->isHTMLElement() || node->id() != ID_SPAN)
185 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
186 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
187 return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
190 static bool isStyleSpan(const NodeImpl *node)
192 if (!node || !node->isHTMLElement())
195 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
196 return elem->id() == ID_SPAN && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
199 static bool isEmptyFontTag(const NodeImpl *node)
201 if (!node || node->id() != ID_FONT)
204 const ElementImpl *elem = static_cast<const ElementImpl *>(node);
205 NamedAttrMapImpl *map = elem->attributes(true); // true for read-only
206 return (!map || map->length() == 1) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
209 static DOMString &blockPlaceholderClassString()
211 static DOMString blockPlaceholderClassString = "khtml-block-placeholder";
212 return blockPlaceholderClassString;
215 static DOMString &matchNearestBlockquoteColorString()
217 static DOMString matchNearestBlockquoteColorString = "match";
218 return matchNearestBlockquoteColorString;
221 static void derefNodesInList(QPtrList<NodeImpl> &list)
223 for (QPtrListIterator<NodeImpl> it(list); it.current(); ++it)
224 it.current()->deref();
227 static void debugPosition(const char *prefix, const Position &pos)
232 LOG(Editing, "%s <null>", prefix);
234 LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
237 static void debugNode(const char *prefix, const NodeImpl *node)
242 LOG(Editing, "%s <null>", prefix);
244 LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node);
247 //------------------------------------------------------------------------------------------
250 EditCommandPtr::EditCommandPtr()
254 EditCommandPtr::EditCommandPtr(EditCommand *impl) : SharedPtr<EditCommand>(impl)
258 EditCommandPtr::EditCommandPtr(const EditCommandPtr &o) : SharedPtr<EditCommand>(o)
262 EditCommandPtr::~EditCommandPtr()
266 EditCommandPtr &EditCommandPtr::operator=(const EditCommandPtr &c)
268 static_cast<SharedPtr<EditCommand> &>(*this) = c;
272 bool EditCommandPtr::isCompositeStep() const
274 IF_IMPL_NULL_RETURN_ARG(false);
275 return get()->isCompositeStep();
278 bool EditCommandPtr::isInsertTextCommand() const
280 IF_IMPL_NULL_RETURN_ARG(false);
281 return get()->isInsertTextCommand();
284 bool EditCommandPtr::isTypingCommand() const
286 IF_IMPL_NULL_RETURN_ARG(false);
287 return get()->isTypingCommand();
290 void EditCommandPtr::apply() const
296 void EditCommandPtr::unapply() const
302 void EditCommandPtr::reapply() const
308 EditAction EditCommandPtr::editingAction() const
310 IF_IMPL_NULL_RETURN_ARG(EditActionUnspecified);
311 return get()->editingAction();
314 DocumentImpl * const EditCommandPtr::document() const
316 IF_IMPL_NULL_RETURN_ARG(0);
317 return get()->document();
320 Selection EditCommandPtr::startingSelection() const
322 IF_IMPL_NULL_RETURN_ARG(Selection());
323 return get()->startingSelection();
326 Selection EditCommandPtr::endingSelection() const
328 IF_IMPL_NULL_RETURN_ARG(Selection());
329 return get()->endingSelection();
332 void EditCommandPtr::setStartingSelection(const Selection &s) const
335 get()->setStartingSelection(s);
338 void EditCommandPtr::setStartingSelection(const VisiblePosition &p) const
341 get()->setStartingSelection(p);
344 void EditCommandPtr::setStartingSelection(const Position &p, EAffinity affinity) const
347 Selection s = Selection(p, affinity);
348 get()->setStartingSelection(s);
351 void EditCommandPtr::setEndingSelection(const Selection &s) const
354 get()->setEndingSelection(s);
357 void EditCommandPtr::setEndingSelection(const VisiblePosition &p) const
360 get()->setStartingSelection(p);
363 void EditCommandPtr::setEndingSelection(const Position &p, EAffinity affinity) const
366 Selection s = Selection(p, affinity);
367 get()->setEndingSelection(s);
370 CSSMutableStyleDeclarationImpl *EditCommandPtr::typingStyle() const
372 IF_IMPL_NULL_RETURN_ARG(0);
373 return get()->typingStyle();
376 void EditCommandPtr::setTypingStyle(CSSMutableStyleDeclarationImpl *style) const
379 get()->setTypingStyle(style);
382 EditCommandPtr EditCommandPtr::parent() const
384 IF_IMPL_NULL_RETURN_ARG(0);
385 return get()->parent();
388 void EditCommandPtr::setParent(const EditCommandPtr &cmd) const
391 get()->setParent(cmd.get());
394 EditCommandPtr &EditCommandPtr::emptyCommand()
396 static EditCommandPtr m_emptyCommand;
397 return m_emptyCommand;
400 //------------------------------------------------------------------------------------------
403 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, ELegacyHTMLStyles usesLegacyStyles)
404 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
406 init(style, Position());
409 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position, ELegacyHTMLStyles usesLegacyStyles)
410 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
412 init(style, position);
415 void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
418 CSSMutableStyleDeclarationImpl *mutableStyle = style->makeMutable();
422 QString styleText("");
424 QValueListConstIterator<CSSProperty> end;
425 for (QValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
426 const CSSProperty *property = &*it;
428 // If position is empty or the position passed in already has the
429 // style, just move on.
430 if (position.isNotNull() && currentlyHasStyle(position, property))
433 // If needed, figure out if this change is a legacy HTML style change.
434 if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property))
439 if (property->id() == CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT) {
440 // we have to special-case text decorations
441 CSSProperty alteredProperty = CSSProperty(CSS_PROP_TEXT_DECORATION, property->value(), property->isImportant());
442 styleText += alteredProperty.cssText().string();
444 styleText += property->cssText().string();
448 mutableStyle->deref();
450 // Save the result for later
451 m_cssStyle = styleText.stripWhiteSpace();
454 StyleChange::ELegacyHTMLStyles StyleChange::styleModeForParseMode(bool isQuirksMode)
456 return isQuirksMode ? UseLegacyHTMLStyles : DoNotUseLegacyHTMLStyles;
459 bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty *property)
461 DOMString valueText(property->value()->cssText());
462 switch (property->id()) {
463 case CSS_PROP_FONT_WEIGHT:
464 if (strcasecmp(valueText, "bold") == 0) {
469 case CSS_PROP_FONT_STYLE:
470 if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) {
471 m_applyItalic = true;
475 case CSS_PROP_COLOR: {
476 QColor color(CSSParser::parseColor(valueText));
477 m_applyFontColor = color.name();
480 case CSS_PROP_FONT_FAMILY:
481 m_applyFontFace = valueText;
483 case CSS_PROP_FONT_SIZE:
484 if (property->value()->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
485 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(property->value());
486 float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
488 m_applyFontSize = "1";
489 else if (number <= 10)
490 m_applyFontSize = "2";
491 else if (number <= 13)
492 m_applyFontSize = "3";
493 else if (number <= 16)
494 m_applyFontSize = "4";
495 else if (number <= 18)
496 m_applyFontSize = "5";
497 else if (number <= 24)
498 m_applyFontSize = "6";
500 m_applyFontSize = "7";
501 // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write
502 // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all,
503 // like Eudora). Yes, they write out *both*. We need to write out both as well. Return false.
507 // Can't make sense of the number. Put no font size.
514 bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
516 ASSERT(pos.isNotNull());
517 CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
520 CSSValueImpl *value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
525 bool result = strcasecmp(value->cssText(), property->value()->cssText()) == 0;
530 //------------------------------------------------------------------------------------------
533 EditCommand::EditCommand(DocumentImpl *document)
534 : m_document(document), m_state(NotApplied), m_typingStyle(0), m_parent(0)
537 ASSERT(m_document->part());
539 m_startingSelection = m_document->part()->selection();
540 m_endingSelection = m_startingSelection;
542 m_document->part()->setSelection(Selection(), false, true);
545 EditCommand::~EditCommand()
550 m_typingStyle->deref();
553 void EditCommand::apply()
556 ASSERT(m_document->part());
557 ASSERT(state() == NotApplied);
559 KHTMLPart *part = m_document->part();
561 ASSERT(part->selection().isNone());
567 // FIXME: Improve typing style.
568 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
569 if (!preservesTypingStyle())
572 if (!isCompositeStep()) {
573 document()->updateLayout();
574 EditCommandPtr cmd(this);
575 part->appliedEditing(cmd);
579 void EditCommand::unapply()
582 ASSERT(m_document->part());
583 ASSERT(state() == Applied);
585 bool topLevel = !isCompositeStep();
587 KHTMLPart *part = m_document->part();
590 part->setSelection(Selection(), false, true);
592 ASSERT(part->selection().isNone());
596 m_state = NotApplied;
599 document()->updateLayout();
600 EditCommandPtr cmd(this);
601 part->unappliedEditing(cmd);
605 void EditCommand::reapply()
608 ASSERT(m_document->part());
609 ASSERT(state() == NotApplied);
611 bool topLevel = !isCompositeStep();
613 KHTMLPart *part = m_document->part();
616 part->setSelection(Selection(), false, true);
618 ASSERT(part->selection().isNone());
625 document()->updateLayout();
626 EditCommandPtr cmd(this);
627 part->reappliedEditing(cmd);
631 void EditCommand::doReapply()
636 EditAction EditCommand::editingAction() const
638 return EditActionUnspecified;
641 void EditCommand::setStartingSelection(const Selection &s)
643 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
644 cmd->m_startingSelection = s;
647 void EditCommand::setStartingSelection(const VisiblePosition &p)
649 Selection s = Selection(p);
650 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
651 cmd->m_startingSelection = s;
654 void EditCommand::setStartingSelection(const Position &p, EAffinity affinity)
656 Selection s = Selection(p, affinity);
657 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
658 cmd->m_startingSelection = s;
661 void EditCommand::setEndingSelection(const Selection &s)
663 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
664 cmd->m_endingSelection = s;
667 void EditCommand::setEndingSelection(const VisiblePosition &p)
669 Selection s = Selection(p);
670 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
671 cmd->m_endingSelection = s;
674 void EditCommand::setEndingSelection(const Position &p, EAffinity affinity)
676 Selection s = Selection(p, affinity);
677 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
678 cmd->m_endingSelection = s;
681 void EditCommand::assignTypingStyle(CSSMutableStyleDeclarationImpl *style)
683 if (m_typingStyle == style)
686 CSSMutableStyleDeclarationImpl *old = m_typingStyle;
687 m_typingStyle = style;
689 m_typingStyle->ref();
694 void EditCommand::setTypingStyle(CSSMutableStyleDeclarationImpl *style)
696 // FIXME: Improve typing style.
697 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
698 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
699 cmd->assignTypingStyle(style);
702 bool EditCommand::preservesTypingStyle() const
707 bool EditCommand::isInsertTextCommand() const
712 bool EditCommand::isTypingCommand() const
717 CSSMutableStyleDeclarationImpl *EditCommand::styleAtPosition(const Position &pos)
719 CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
720 computedStyle->ref();
721 CSSMutableStyleDeclarationImpl *style = computedStyle->copyInheritableProperties();
722 computedStyle->deref();
724 // FIXME: Improve typing style.
725 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
726 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
728 style->merge(typingStyle);
734 //------------------------------------------------------------------------------------------
735 // CompositeEditCommand
737 CompositeEditCommand::CompositeEditCommand(DocumentImpl *document)
738 : EditCommand(document)
742 void CompositeEditCommand::doUnapply()
744 if (m_cmds.count() == 0) {
748 for (int i = m_cmds.count() - 1; i >= 0; --i)
749 m_cmds[i]->unapply();
751 setState(NotApplied);
754 void CompositeEditCommand::doReapply()
756 if (m_cmds.count() == 0) {
760 for (QValueList<EditCommandPtr>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
767 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
769 void CompositeEditCommand::applyCommandToComposite(EditCommandPtr &cmd)
771 cmd.setStartingSelection(endingSelection());
772 cmd.setEndingSelection(endingSelection());
778 void CompositeEditCommand::applyStyle(CSSStyleDeclarationImpl *style, EditAction editingAction)
780 EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction));
781 applyCommandToComposite(cmd);
784 void CompositeEditCommand::insertParagraphSeparator()
786 EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
787 applyCommandToComposite(cmd);
790 void CompositeEditCommand::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
792 ASSERT(refChild->id() != ID_BODY);
793 EditCommandPtr cmd(new InsertNodeBeforeCommand(document(), insertChild, refChild));
794 applyCommandToComposite(cmd);
797 void CompositeEditCommand::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
799 ASSERT(refChild->id() != ID_BODY);
800 if (refChild->parentNode()->lastChild() == refChild) {
801 appendNode(insertChild, refChild->parentNode());
804 ASSERT(refChild->nextSibling());
805 insertNodeBefore(insertChild, refChild->nextSibling());
809 void CompositeEditCommand::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
811 if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
812 NodeImpl *child = refChild->firstChild();
813 for (long i = 0; child && i < offset; i++)
814 child = child->nextSibling();
816 insertNodeBefore(insertChild, child);
818 appendNode(insertChild, refChild);
820 else if (refChild->caretMinOffset() >= offset) {
821 insertNodeBefore(insertChild, refChild);
823 else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
824 splitTextNode(static_cast<TextImpl *>(refChild), offset);
825 insertNodeBefore(insertChild, refChild);
828 insertNodeAfter(insertChild, refChild);
832 void CompositeEditCommand::appendNode(NodeImpl *appendChild, NodeImpl *parent)
834 EditCommandPtr cmd(new AppendNodeCommand(document(), appendChild, parent));
835 applyCommandToComposite(cmd);
838 void CompositeEditCommand::removeFullySelectedNode(NodeImpl *node)
840 if (isTableStructureNode(node)) {
841 // Do not remove an element of table structure; remove its contents.
842 NodeImpl *child = node->firstChild();
844 NodeImpl *remove = child;
845 child = child->nextSibling();
846 removeFullySelectedNode(remove);
850 EditCommandPtr cmd(new RemoveNodeCommand(document(), node));
851 applyCommandToComposite(cmd);
855 void CompositeEditCommand::removeNode(NodeImpl *removeChild)
857 EditCommandPtr cmd(new RemoveNodeCommand(document(), removeChild));
858 applyCommandToComposite(cmd);
861 void CompositeEditCommand::removeNodePreservingChildren(NodeImpl *removeChild)
863 EditCommandPtr cmd(new RemoveNodePreservingChildrenCommand(document(), removeChild));
864 applyCommandToComposite(cmd);
867 void CompositeEditCommand::splitTextNode(TextImpl *text, long offset)
869 EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, offset));
870 applyCommandToComposite(cmd);
873 void CompositeEditCommand::splitElement(ElementImpl *element, NodeImpl *atChild)
875 EditCommandPtr cmd(new SplitElementCommand(document(), element, atChild));
876 applyCommandToComposite(cmd);
879 void CompositeEditCommand::mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second)
881 EditCommandPtr cmd(new MergeIdenticalElementsCommand(document(), first, second));
882 applyCommandToComposite(cmd);
885 void CompositeEditCommand::wrapContentsInDummySpan(DOM::ElementImpl *element)
887 EditCommandPtr cmd(new WrapContentsInDummySpanCommand(document(), element));
888 applyCommandToComposite(cmd);
891 void CompositeEditCommand::splitTextNodeContainingElement(DOM::TextImpl *text, long offset)
893 EditCommandPtr cmd(new SplitTextNodeContainingElementCommand(document(), text, offset));
894 applyCommandToComposite(cmd);
897 void CompositeEditCommand::joinTextNodes(TextImpl *text1, TextImpl *text2)
899 EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2));
900 applyCommandToComposite(cmd);
903 void CompositeEditCommand::inputText(const DOMString &text, bool selectInsertedText)
905 InsertTextCommand *impl = new InsertTextCommand(document());
906 EditCommandPtr cmd(impl);
907 applyCommandToComposite(cmd);
908 impl->input(text, selectInsertedText);
911 void CompositeEditCommand::insertTextIntoNode(TextImpl *node, long offset, const DOMString &text)
913 EditCommandPtr cmd(new InsertIntoTextNode(document(), node, offset, text));
914 applyCommandToComposite(cmd);
917 void CompositeEditCommand::deleteTextFromNode(TextImpl *node, long offset, long count)
919 EditCommandPtr cmd(new DeleteFromTextNodeCommand(document(), node, offset, count));
920 applyCommandToComposite(cmd);
923 void CompositeEditCommand::replaceTextInNode(TextImpl *node, long offset, long count, const DOMString &replacementText)
925 EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count));
926 applyCommandToComposite(deleteCommand);
927 EditCommandPtr insertCommand(new InsertIntoTextNode(document(), node, offset, replacementText));
928 applyCommandToComposite(insertCommand);
931 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete)
933 if (endingSelection().isRange()) {
934 EditCommandPtr cmd(new DeleteSelectionCommand(document(), smartDelete, mergeBlocksAfterDelete));
935 applyCommandToComposite(cmd);
939 void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
941 if (selection.isRange()) {
942 EditCommandPtr cmd(new DeleteSelectionCommand(document(), selection, smartDelete, mergeBlocksAfterDelete));
943 applyCommandToComposite(cmd);
947 void CompositeEditCommand::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
949 EditCommandPtr cmd(new RemoveCSSPropertyCommand(document(), decl, property));
950 applyCommandToComposite(cmd);
953 void CompositeEditCommand::removeNodeAttribute(ElementImpl *element, int attribute)
955 DOMString value = element->getAttribute(attribute);
958 EditCommandPtr cmd(new RemoveNodeAttributeCommand(document(), element, attribute));
959 applyCommandToComposite(cmd);
962 void CompositeEditCommand::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
964 EditCommandPtr cmd(new SetNodeAttributeCommand(document(), element, attribute, value));
965 applyCommandToComposite(cmd);
968 void CompositeEditCommand::rebalanceWhitespace()
970 Selection selection = endingSelection();
971 if (selection.isCaretOrRange()) {
972 EditCommandPtr startCmd(new RebalanceWhitespaceCommand(document(), endingSelection().start()));
973 applyCommandToComposite(startCmd);
974 if (selection.isRange()) {
975 EditCommandPtr endCmd(new RebalanceWhitespaceCommand(document(), endingSelection().end()));
976 applyCommandToComposite(endCmd);
981 void CompositeEditCommand::deleteInsignificantText(TextImpl *textNode, int start, int end)
983 if (!textNode || !textNode->renderer() || start >= end)
986 RenderText *textRenderer = static_cast<RenderText *>(textNode->renderer());
987 InlineTextBox *box = textRenderer->firstTextBox();
989 // whole text node is empty
990 removeNode(textNode);
994 long length = textNode->length();
995 if (start >= length || end > length)
999 InlineTextBox *prevBox = 0;
1000 DOMStringImpl *str = 0;
1002 // This loop structure works to process all gaps preceding a box,
1003 // and also will look at the gap after the last box.
1004 while (prevBox || box) {
1005 int gapStart = prevBox ? prevBox->m_start + prevBox->m_len : 0;
1007 // No more chance for any intersections
1010 int gapEnd = box ? box->m_start : length;
1011 bool indicesIntersect = start <= gapEnd && end >= gapStart;
1012 int gapLen = gapEnd - gapStart;
1013 if (indicesIntersect && gapLen > 0) {
1014 gapStart = kMax(gapStart, start);
1015 gapEnd = kMin(gapEnd, end);
1017 str = textNode->string()->substring(start, end - start);
1020 // remove text in the gap
1021 str->remove(gapStart - start - removed, gapLen);
1027 box = box->nextTextBox();
1031 // Replace the text between start and end with our pruned version.
1033 replaceTextInNode(textNode, start, end - start, str);
1036 // Assert that we are not going to delete all of the text in the node.
1037 // If we were, that should have been done above with the call to
1038 // removeNode and return.
1039 ASSERT(start > 0 || (unsigned long)end - start < textNode->length());
1040 deleteTextFromNode(textNode, start, end - start);
1046 void CompositeEditCommand::deleteInsignificantText(const Position &start, const Position &end)
1048 if (start.isNull() || end.isNull())
1051 if (RangeImpl::compareBoundaryPoints(start, end) >= 0)
1054 NodeImpl *node = start.node();
1056 NodeImpl *next = node->traverseNextNode();
1058 if (node->isTextNode()) {
1059 TextImpl *textNode = static_cast<TextImpl *>(node);
1060 bool isStartNode = node == start.node();
1061 bool isEndNode = node == end.node();
1062 int startOffset = isStartNode ? start.offset() : 0;
1063 int endOffset = isEndNode ? end.offset() : textNode->length();
1064 deleteInsignificantText(textNode, startOffset, endOffset);
1067 if (node == end.node())
1073 void CompositeEditCommand::deleteInsignificantTextDownstream(const DOM::Position &pos)
1075 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream(StayInBlock);
1076 deleteInsignificantText(pos, end);
1079 void CompositeEditCommand::insertBlockPlaceholder(NodeImpl *node)
1084 ASSERT(node->renderer() && node->renderer()->isBlockFlow());
1086 appendNode(createBlockPlaceholderElement(document()), node);
1089 bool CompositeEditCommand::insertBlockPlaceholderIfNeeded(NodeImpl *node)
1094 document()->updateLayout();
1096 RenderObject *renderer = node->renderer();
1097 if (!renderer || !renderer->isBlockFlow())
1100 if (renderer->height() > 0)
1103 insertBlockPlaceholder(node);
1107 bool CompositeEditCommand::removeBlockPlaceholderIfNeeded(NodeImpl *node)
1109 NodeImpl *placeholder = findBlockPlaceholder(node);
1111 removeNode(placeholder);
1117 NodeImpl *CompositeEditCommand::findBlockPlaceholder(NodeImpl *node)
1122 document()->updateLayout();
1124 RenderObject *renderer = node->renderer();
1125 if (!renderer || !renderer->isBlockFlow())
1128 for (NodeImpl *checkMe = node; checkMe; checkMe = checkMe->traverseNextNode(node)) {
1129 if (checkMe->isElementNode()) {
1130 ElementImpl *element = static_cast<ElementImpl *>(checkMe);
1131 if (element->enclosingBlockFlowElement() == node &&
1132 element->getAttribute(ATTR_CLASS) == blockPlaceholderClassString()) {
1141 void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position &pos)
1146 document()->updateLayout();
1148 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
1149 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
1150 VisiblePosition visibleParagraphEnd(endOfParagraph(visiblePos, IncludeLineBreak));
1151 Position paragraphStart = visibleParagraphStart.deepEquivalent().upstream(StayInBlock);
1152 Position paragraphEnd = visibleParagraphEnd.deepEquivalent().upstream(StayInBlock);
1153 Position beforeParagraphStart = paragraphStart.upstream(DoNotStayInBlock);
1155 // Perform some checks to see if we need to perform work in this function.
1156 if (paragraphStart.node()->isBlockFlow()) {
1157 if (paragraphEnd.node()->isBlockFlow()) {
1158 if (!paragraphEnd.node()->isAncestor(paragraphStart.node())) {
1159 // If the paragraph end is a descendant of paragraph start, then we need to run
1160 // the rest of this function. If not, we can bail here.
1164 else if (paragraphEnd.node()->enclosingBlockFlowElement() != paragraphStart.node()) {
1165 // The paragraph end is in another block that is an ancestor of the paragraph start.
1166 // We can bail as we have a full block to work with.
1167 ASSERT(paragraphStart.node()->isAncestor(paragraphEnd.node()->enclosingBlockFlowElement()));
1170 else if (isEndOfDocument(visibleParagraphEnd)) {
1171 // At the end of the document. We can bail here as well.
1176 // Create the block to insert. Most times, this will be a shallow clone of the block containing
1177 // the start of the selection (the start block), except for two cases:
1178 // 1) When the start block is a body element.
1179 // 2) When the start block is a mail blockquote and we are not in a position to insert
1180 // the new block as a peer of the start block. This prevents creating an unwanted
1181 // additional level of quoting.
1182 NodeImpl *startBlock = paragraphStart.node()->enclosingBlockFlowElement();
1183 NodeImpl *newBlock = 0;
1184 if (startBlock->id() == ID_BODY || (isMailBlockquote(startBlock) && paragraphStart.node() != startBlock))
1185 newBlock = createDefaultParagraphElement(document());
1187 newBlock = startBlock->cloneNode(false);
1189 NodeImpl *moveNode = paragraphStart.node();
1190 if (paragraphStart.offset() >= paragraphStart.node()->caretMaxOffset())
1191 moveNode = moveNode->traverseNextNode();
1192 NodeImpl *endNode = paragraphEnd.node();
1194 if (paragraphStart.node()->id() == ID_BODY) {
1195 insertNodeAt(newBlock, paragraphStart.node(), 0);
1197 else if (paragraphStart.node()->id() == ID_BR) {
1198 insertNodeAfter(newBlock, paragraphStart.node());
1200 else if (paragraphStart.node()->isBlockFlow()) {
1201 insertNodeBefore(newBlock, paragraphStart.node());
1203 else if (beforeParagraphStart.node()->enclosingBlockFlowElement()->id() != ID_BODY) {
1204 insertNodeAfter(newBlock, beforeParagraphStart.node()->enclosingBlockFlowElement());
1207 insertNodeAfter(newBlock, beforeParagraphStart.node());
1210 while (moveNode && !moveNode->isBlockFlow()) {
1211 NodeImpl *next = moveNode->traverseNextNode();
1212 removeNode(moveNode);
1213 appendNode(moveNode, newBlock);
1214 if (moveNode == endNode)
1220 //==========================================================================================
1221 // Concrete commands
1222 //------------------------------------------------------------------------------------------
1223 // AppendNodeCommand
1225 AppendNodeCommand::AppendNodeCommand(DocumentImpl *document, NodeImpl *appendChild, NodeImpl *parentNode)
1226 : EditCommand(document), m_appendChild(appendChild), m_parentNode(parentNode)
1228 ASSERT(m_appendChild);
1229 m_appendChild->ref();
1231 ASSERT(m_parentNode);
1232 m_parentNode->ref();
1235 AppendNodeCommand::~AppendNodeCommand()
1237 ASSERT(m_appendChild);
1238 m_appendChild->deref();
1240 ASSERT(m_parentNode);
1241 m_parentNode->deref();
1244 void AppendNodeCommand::doApply()
1246 ASSERT(m_appendChild);
1247 ASSERT(m_parentNode);
1249 int exceptionCode = 0;
1250 m_parentNode->appendChild(m_appendChild, exceptionCode);
1251 ASSERT(exceptionCode == 0);
1254 void AppendNodeCommand::doUnapply()
1256 ASSERT(m_appendChild);
1257 ASSERT(m_parentNode);
1258 ASSERT(state() == Applied);
1260 int exceptionCode = 0;
1261 m_parentNode->removeChild(m_appendChild, exceptionCode);
1262 ASSERT(exceptionCode == 0);
1265 //------------------------------------------------------------------------------------------
1266 // ApplyStyleCommand
1268 ApplyStyleCommand::ApplyStyleCommand(DocumentImpl *document, CSSStyleDeclarationImpl *style, EditAction editingAction, EPropertyLevel propertyLevel)
1269 : CompositeEditCommand(document), m_style(style->makeMutable()), m_editingAction(editingAction), m_propertyLevel(propertyLevel)
1275 ApplyStyleCommand::~ApplyStyleCommand()
1281 void ApplyStyleCommand::doApply()
1283 switch (m_propertyLevel) {
1284 case PropertyDefault: {
1285 // apply the block-centric properties of the style
1286 CSSMutableStyleDeclarationImpl *blockStyle = m_style->copyBlockProperties();
1288 applyBlockStyle(blockStyle);
1289 // apply any remaining styles to the inline elements
1290 // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
1291 if (blockStyle->length() < m_style->length()) {
1292 CSSMutableStyleDeclarationImpl *inlineStyle = m_style->copy();
1294 applyRelativeFontStyleChange(inlineStyle);
1295 blockStyle->diff(inlineStyle);
1296 applyInlineStyle(inlineStyle);
1297 inlineStyle->deref();
1299 blockStyle->deref();
1302 case ForceBlockProperties:
1303 // Force all properties to be applied as block styles.
1304 applyBlockStyle(m_style);
1308 setEndingSelectionNeedsLayout();
1311 EditAction ApplyStyleCommand::editingAction() const
1313 return m_editingAction;
1316 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclarationImpl *style)
1318 // update document layout once before removing styles
1319 // so that we avoid the expense of updating before each and every call
1320 // to check a computed style
1321 document()->updateLayout();
1323 // get positions we want to use for applying style
1324 Position start(endingSelection().start());
1325 Position end(endingSelection().end());
1327 // remove current values, if any, of the specified styles from the blocks
1328 // NOTE: tracks the previous block to avoid repeated processing
1329 NodeImpl *beyondEnd = end.node()->traverseNextNode();
1330 NodeImpl *prevBlock = 0;
1331 for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode()) {
1332 NodeImpl *block = node->enclosingBlockFlowElement();
1333 if (block != prevBlock && block->isHTMLElement()) {
1334 removeCSSStyle(style, static_cast<HTMLElementImpl *>(block));
1339 // apply specified styles to the block flow elements in the selected range
1341 NodeImpl *node = start.node();
1342 while (node != beyondEnd) {
1343 NodeImpl *next = node->traverseNextNode();
1344 if (node->renderer()) {
1345 NodeImpl *block = node->enclosingBlockFlowElement();
1346 if (block != prevBlock) {
1347 addBlockStyleIfNeeded(style, node);
1355 #define NoFontDelta (0.0f)
1356 #define MinimumFontSize (0.1f)
1358 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclarationImpl *style)
1360 if (style->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
1361 // Explicit font size overrides any delta.
1362 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1366 // Get the adjustment amount out of the style.
1367 CSSValueImpl *value = style->getPropertyCSSValue(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1371 float adjustment = NoFontDelta;
1372 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
1373 CSSPrimitiveValueImpl *primitiveValue = static_cast<CSSPrimitiveValueImpl *>(value);
1374 if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
1375 // Only PX handled now. If we handle more types in the future, perhaps
1376 // a switch statement here would be more appropriate.
1377 adjustment = primitiveValue->getFloatValue(CSSPrimitiveValue::CSS_PX);
1380 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1382 if (adjustment == NoFontDelta)
1385 // Adjust to the positions we want to use for applying style.
1386 Selection selection = endingSelection();
1387 Position start(selection.start().downstream(StayInBlock));
1388 Position end(selection.end().upstream(StayInBlock));
1389 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
1390 Position swap = start;
1395 // Join up any adjacent text nodes.
1396 if (start.node()->isTextNode()) {
1397 joinChildTextNodes(start.node()->parentNode(), start, end);
1398 selection = endingSelection();
1399 start = selection.start();
1400 end = selection.end();
1402 if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
1403 joinChildTextNodes(end.node()->parentNode(), start, end);
1404 selection = endingSelection();
1405 start = selection.start();
1406 end = selection.end();
1409 // Split the start text nodes if needed to apply style.
1410 bool splitStart = splitTextAtStartIfNeeded(start, end);
1412 start = endingSelection().start();
1413 end = endingSelection().end();
1415 bool splitEnd = splitTextAtEndIfNeeded(start, end);
1417 start = endingSelection().start();
1418 end = endingSelection().end();
1421 NodeImpl *beyondEnd = end.node()->traverseNextNode(); // Calculate loop end point.
1422 start = start.upstream(StayInBlock); // Move upstream to ensure we do not add redundant spans.
1424 // Store away font size before making any changes to the document.
1425 // This ensures that changes to one node won't effect another.
1426 QMap<const NodeImpl *,float> startingFontSizes;
1427 for (const NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode())
1428 startingFontSizes.insert(node, computedFontSize(node));
1430 // These spans were added by us. If empty after font size changes, they can be removed.
1431 QPtrList<NodeImpl> emptySpans;
1433 NodeImpl *lastStyledNode = 0;
1434 for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode()) {
1435 HTMLElementImpl *elem = 0;
1436 if (node->isHTMLElement()) {
1437 // Only work on fully selected nodes.
1438 if (!nodeFullySelected(node, start, end))
1440 elem = static_cast<HTMLElementImpl *>(node);
1442 else if (node->isTextNode() && node->parentNode() != lastStyledNode) {
1443 // Last styled node was not parent node of this text node, but we wish to style this
1444 // text node. To make this possible, add a style span to surround this text node.
1445 elem = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
1446 insertNodeBefore(elem, node);
1447 surroundNodeRangeWithElement(node, node, elem);
1450 // Only handle HTML elements and text nodes.
1453 lastStyledNode = node;
1455 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->getInlineStyleDecl();
1456 float currentFontSize = computedFontSize(node);
1457 float desiredFontSize = kMax(MinimumFontSize, startingFontSizes[node] + adjustment);
1458 if (inlineStyleDecl->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
1459 inlineStyleDecl->removeProperty(CSS_PROP_FONT_SIZE, true);
1460 currentFontSize = computedFontSize(node);
1462 if (currentFontSize != desiredFontSize) {
1463 QString desiredFontSizeString = QString::number(desiredFontSize);
1464 desiredFontSizeString += "px";
1465 inlineStyleDecl->setProperty(CSS_PROP_FONT_SIZE, desiredFontSizeString, false, false);
1466 setNodeAttribute(elem, ATTR_STYLE, inlineStyleDecl->cssText());
1468 if (inlineStyleDecl->length() == 0) {
1469 removeNodeAttribute(elem, ATTR_STYLE);
1470 if (isEmptyStyleSpan(elem))
1471 emptySpans.append(elem);
1475 for (QPtrListIterator<NodeImpl> it(emptySpans); it.current(); ++it)
1476 removeNodePreservingChildren(it.current());
1480 #undef MinimumFontSize
1482 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style)
1484 // adjust to the positions we want to use for applying style
1485 Position start(endingSelection().start().downstream(StayInBlock).equivalentRangeCompliantPosition());
1486 Position end(endingSelection().end().upstream(StayInBlock));
1487 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
1488 Position swap = start;
1493 // update document layout once before removing styles
1494 // so that we avoid the expense of updating before each and every call
1495 // to check a computed style
1496 document()->updateLayout();
1498 // split the start node and containing element if the selection starts inside of it
1499 bool splitStart = splitTextElementAtStartIfNeeded(start, end);
1501 start = endingSelection().start();
1502 end = endingSelection().end();
1505 // split the end node and containing element if the selection ends inside of it
1506 bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
1507 start = endingSelection().start();
1508 end = endingSelection().end();
1510 // Remove style from the selection.
1511 // Use the upstream position of the start for removing style.
1512 // This will ensure we remove all traces of the relevant styles from the selection
1513 // and prevent us from adding redundant ones, as described in:
1514 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
1515 removeInlineStyle(style, start.upstream(StayInBlock), end);
1518 bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
1520 start = endingSelection().start();
1521 end = endingSelection().end();
1526 mergeEndWithNextIfIdentical(start, end);
1527 start = endingSelection().start();
1528 end = endingSelection().end();
1531 // update document layout once before running the rest of the function
1532 // so that we avoid the expense of updating before each and every call
1533 // to check a computed style
1534 document()->updateLayout();
1536 if (start.node() == end.node()) {
1537 // simple case...start and end are the same node
1538 addInlineStyleIfNeeded(style, start.node(), end.node());
1541 NodeImpl *node = start.node();
1542 if (start.offset() >= start.node()->caretMaxOffset())
1543 node = node->traverseNextNode();
1545 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
1546 NodeImpl *runStart = node;
1548 NodeImpl *next = node->traverseNextNode();
1549 // Break if node is the end node, or if the next node does not fit in with
1550 // the current group.
1551 if (node == end.node() ||
1552 runStart->parentNode() != next->parentNode() ||
1553 (next->isHTMLElement() && next->id() != ID_BR) ||
1554 (next->renderer() && !next->renderer()->isInline()))
1558 // Now apply style to the run we found.
1559 addInlineStyleIfNeeded(style, runStart, node);
1561 if (node == end.node())
1563 node = node->traverseNextNode();
1567 if (splitStart || splitEnd) {
1568 cleanUpEmptyStyleSpans(start, end);
1572 //------------------------------------------------------------------------------------------
1573 // ApplyStyleCommand: style-removal helpers
1575 bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1577 QValueListConstIterator<CSSProperty> end;
1578 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1579 switch ((*it).id()) {
1580 case CSS_PROP_FONT_WEIGHT:
1581 if (elem->id() == ID_B)
1584 case CSS_PROP_FONT_STYLE:
1585 if (elem->id() == ID_I)
1593 void ApplyStyleCommand::removeHTMLStyleNode(HTMLElementImpl *elem)
1595 // This node can be removed.
1596 // EDIT FIXME: This does not handle the case where the node
1597 // has attributes. But how often do people add attributes to <B> tags?
1598 // Not so often I think.
1600 removeNodePreservingChildren(elem);
1603 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1608 if (elem->id() != ID_FONT)
1611 int exceptionCode = 0;
1612 QValueListConstIterator<CSSProperty> end;
1613 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1614 switch ((*it).id()) {
1615 case CSS_PROP_COLOR:
1616 elem->removeAttribute(ATTR_COLOR, exceptionCode);
1617 ASSERT(exceptionCode == 0);
1619 case CSS_PROP_FONT_FAMILY:
1620 elem->removeAttribute(ATTR_FACE, exceptionCode);
1621 ASSERT(exceptionCode == 0);
1623 case CSS_PROP_FONT_SIZE:
1624 elem->removeAttribute(ATTR_SIZE, exceptionCode);
1625 ASSERT(exceptionCode == 0);
1630 if (isEmptyFontTag(elem))
1631 removeNodePreservingChildren(elem);
1634 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1639 CSSMutableStyleDeclarationImpl *decl = elem->inlineStyleDecl();
1643 QValueListConstIterator<CSSProperty> end;
1644 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1645 int propertyID = (*it).id();
1646 CSSValueImpl *value = decl->getPropertyCSSValue(propertyID);
1649 removeCSSProperty(decl, propertyID);
1654 if (isEmptyStyleSpan(elem))
1655 removeNodePreservingChildren(elem);
1658 void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
1660 ASSERT(start.isNotNull());
1661 ASSERT(end.isNotNull());
1662 ASSERT(start.node()->inDocument());
1663 ASSERT(end.node()->inDocument());
1664 ASSERT(RangeImpl::compareBoundaryPoints(start, end) <= 0);
1668 static bool hasTextDecorationProperty(NodeImpl *node)
1670 if (!node->isElementNode())
1673 ElementImpl *element = static_cast<ElementImpl *>(node);
1674 CSSComputedStyleDeclarationImpl style(element);
1676 CSSValueImpl *value = style.getPropertyCSSValue(CSS_PROP_TEXT_DECORATION, DoNotUpdateLayout);
1680 DOMString valueText(value->cssText());
1682 if (strcasecmp(valueText,"none") != 0)
1689 static NodeImpl* highestAncestorWithTextDecoration(NodeImpl *node)
1691 NodeImpl *result = NULL;
1693 for (NodeImpl *n = node; n; n = n->parentNode()) {
1694 if (hasTextDecorationProperty(n))
1701 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractTextDecorationStyle(NodeImpl *node)
1704 ASSERT(node->isElementNode());
1706 // non-html elements not handled yet
1707 if (!node->isHTMLElement())
1710 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
1711 CSSMutableStyleDeclarationImpl *style = element->inlineStyleDecl();
1716 int properties[1] = { CSS_PROP_TEXT_DECORATION };
1717 CSSMutableStyleDeclarationImpl *textDecorationStyle = style->copyPropertiesInSet(properties, 1);
1719 CSSValueImpl *property = style->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
1720 if (property && strcasecmp(property->cssText(), "none") != 0) {
1721 removeCSSProperty(style, CSS_PROP_TEXT_DECORATION);
1726 return textDecorationStyle;
1729 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractAndNegateTextDecorationStyle(NodeImpl *node)
1732 ASSERT(node->isElementNode());
1734 // non-html elements not handled yet
1735 if (!node->isHTMLElement())
1738 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
1739 CSSComputedStyleDeclarationImpl *computedStyle = new CSSComputedStyleDeclarationImpl(element);
1740 ASSERT(computedStyle);
1742 computedStyle->ref();
1744 int properties[1] = { CSS_PROP_TEXT_DECORATION };
1745 CSSMutableStyleDeclarationImpl *textDecorationStyle = computedStyle->copyPropertiesInSet(properties, 1);
1748 CSSValueImpl *property = computedStyle->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
1749 if (property && strcasecmp(property->cssText(), "none") != 0) {
1751 CSSMutableStyleDeclarationImpl *newStyle = textDecorationStyle->copy();
1754 newStyle->setProperty(CSS_PROP_TEXT_DECORATION, "none");
1755 applyTextDecorationStyle(node, newStyle);
1761 computedStyle->deref();
1763 return textDecorationStyle;
1766 void ApplyStyleCommand::applyTextDecorationStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style)
1770 if (!style || !style->cssText().length())
1773 if (node->isTextNode()) {
1774 HTMLElementImpl *styleSpan = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
1775 insertNodeBefore(styleSpan, node);
1776 surroundNodeRangeWithElement(node, node, styleSpan);
1780 if (!node->isElementNode())
1783 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
1785 StyleChange styleChange(style, Position(element, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
1786 if (styleChange.cssStyle().length() > 0) {
1787 DOMString cssText = styleChange.cssStyle();
1788 CSSMutableStyleDeclarationImpl *decl = element->inlineStyleDecl();
1790 cssText += decl->cssText();
1791 setNodeAttribute(element, ATTR_STYLE, cssText);
1795 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(NodeImpl *node, const Position &start, const Position &end, bool force)
1797 NodeImpl *highestAncestor = highestAncestorWithTextDecoration(node);
1799 if (highestAncestor) {
1800 NodeImpl *nextCurrent;
1801 NodeImpl *nextChild;
1802 for (NodeImpl *current = highestAncestor; current != node; current = nextCurrent) {
1807 CSSMutableStyleDeclarationImpl *decoration = force ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
1811 for (NodeImpl *child = current->firstChild(); child; child = nextChild) {
1812 nextChild = child->nextSibling();
1814 if (node == child) {
1815 nextCurrent = child;
1816 } else if (node->isAncestor(child)) {
1817 applyTextDecorationStyle(child, decoration);
1818 nextCurrent = child;
1820 applyTextDecorationStyle(child, decoration);
1825 decoration->deref();
1830 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end)
1832 // We need to work in two passes. First we push down any inline
1833 // styles that set text decoration. Then we look for any remaining
1834 // styles (caused by stylesheets) and explicitly negate text
1835 // decoration while pushing down.
1837 pushDownTextDecorationStyleAroundNode(start.node(), start, end, false);
1838 document()->updateLayout();
1839 pushDownTextDecorationStyleAroundNode(start.node(), start, end, true);
1841 pushDownTextDecorationStyleAroundNode(end.node(), start, end, false);
1842 document()->updateLayout();
1843 pushDownTextDecorationStyleAroundNode(end.node(), start, end, true);
1846 void ApplyStyleCommand::removeInlineStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
1848 ASSERT(start.isNotNull());
1849 ASSERT(end.isNotNull());
1850 ASSERT(start.node()->inDocument());
1851 ASSERT(end.node()->inDocument());
1852 ASSERT(RangeImpl::compareBoundaryPoints(start, end) < 0);
1854 CSSValueImpl *textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT);
1856 if (textDecorationSpecialProperty) {
1857 pushDownTextDecorationStyleAtBoundaries(start.downstream(StayInBlock), end.upstream(StayInBlock));
1858 style = style->copy();
1859 style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT));
1862 NodeImpl *node = start.node();
1864 NodeImpl *next = node->traverseNextNode();
1865 if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
1866 HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
1867 if (isHTMLStyleNode(style, elem)) {
1868 removeHTMLStyleNode(elem);
1871 removeHTMLFontStyle(style, elem);
1872 removeCSSStyle(style, elem);
1875 if (node == end.node())
1881 if (textDecorationSpecialProperty) {
1886 bool ApplyStyleCommand::nodeFullySelected(NodeImpl *node, const Position &start, const Position &end) const
1889 ASSERT(node->isElementNode());
1891 Position pos = Position(node, node->childNodeCount()).upstream();
1892 return RangeImpl::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 &&
1893 RangeImpl::compareBoundaryPoints(pos, end) <= 0;
1896 bool ApplyStyleCommand::nodeFullyUnselected(NodeImpl *node, const Position &start, const Position &end) const
1899 ASSERT(node->isElementNode());
1901 Position pos = Position(node, node->childNodeCount()).upstream();
1902 bool isFullyBeforeStart = RangeImpl::compareBoundaryPoints(pos, start) < 0;
1903 bool isFullyAfterEnd = RangeImpl::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0;
1905 return isFullyBeforeStart || isFullyAfterEnd;
1909 //------------------------------------------------------------------------------------------
1910 // ApplyStyleCommand: style-application helpers
1912 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
1914 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
1915 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
1916 TextImpl *text = static_cast<TextImpl *>(start.node());
1917 splitTextNode(text, start.offset());
1918 setEndingSelection(Selection(Position(start.node(), 0), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
1924 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
1926 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
1927 TextImpl *text = static_cast<TextImpl *>(end.node());
1928 splitTextNode(text, end.offset());
1930 NodeImpl *prevNode = text->previousSibling();
1932 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
1934 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode, prevNode->caretMaxOffset()), SEL_DEFAULT_AFFINITY));
1940 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
1942 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
1943 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
1944 TextImpl *text = static_cast<TextImpl *>(start.node());
1945 splitTextNodeContainingElement(text, start.offset());
1947 setEndingSelection(Selection(Position(start.node()->parentNode(), start.node()->nodeIndex()), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
1953 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
1955 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
1956 TextImpl *text = static_cast<TextImpl *>(end.node());
1957 splitTextNodeContainingElement(text, end.offset());
1959 NodeImpl *prevNode = text->parent()->previousSibling()->lastChild();
1961 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
1963 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode->parent(), prevNode->nodeIndex() + 1), SEL_DEFAULT_AFFINITY));
1969 static bool areIdenticalElements(NodeImpl *first, NodeImpl *second)
1971 // check that tag name and all attribute names and values are identical
1973 if (!first->isElementNode())
1976 if (!second->isElementNode())
1979 ElementImpl *firstElement = static_cast<ElementImpl *>(first);
1980 ElementImpl *secondElement = static_cast<ElementImpl *>(second);
1982 if (firstElement->id() != secondElement->id())
1985 NamedAttrMapImpl *firstMap = firstElement->attributes();
1986 NamedAttrMapImpl *secondMap = secondElement->attributes();
1988 unsigned firstLength = firstMap->length();
1990 if (firstLength != secondMap->length())
1993 for (unsigned i = 0; i < firstLength; i++) {
1994 DOM::AttributeImpl *attribute = firstMap->attributeItem(i);
1995 DOM::AttributeImpl *secondAttribute = secondMap->getAttributeItem(attribute->id());
1997 if (!secondAttribute || attribute->value() != secondAttribute->value())
2004 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
2006 NodeImpl *startNode = start.node();
2007 long startOffset = start.offset();
2009 if (start.node()->isAtomicNode()) {
2010 if (start.offset() != 0)
2013 if (start.node()->previousSibling())
2016 startNode = start.node()->parent();
2020 if (!startNode->isElementNode())
2023 if (startOffset != 0)
2026 NodeImpl *previousSibling = startNode->previousSibling();
2028 if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
2029 ElementImpl *previousElement = static_cast<ElementImpl *>(previousSibling);
2030 ElementImpl *element = static_cast<ElementImpl *>(startNode);
2031 NodeImpl *startChild = element->firstChild();
2033 mergeIdenticalElements(previousElement, element);
2035 long startOffsetAdjustment = startChild->nodeIndex();
2036 long endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
2038 setEndingSelection(Selection(Position(startNode, startOffsetAdjustment), SEL_DEFAULT_AFFINITY,
2039 Position(end.node(), end.offset() + endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
2047 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
2049 NodeImpl *endNode = end.node();
2050 int endOffset = end.offset();
2052 if (endNode->isAtomicNode()) {
2053 if (endOffset < endNode->caretMaxOffset())
2056 unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
2057 if (end.node()->nextSibling())
2060 endNode = end.node()->parent();
2061 endOffset = parentLastOffset;
2064 if (!endNode->isElementNode() || endNode->id() == ID_BR)
2067 NodeImpl *nextSibling = endNode->nextSibling();
2069 if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
2070 ElementImpl *nextElement = static_cast<ElementImpl *>(nextSibling);
2071 ElementImpl *element = static_cast<ElementImpl *>(endNode);
2072 NodeImpl *nextChild = nextElement->firstChild();
2074 mergeIdenticalElements(element, nextElement);
2076 NodeImpl *startNode = start.node() == endNode ? nextElement : start.node();
2079 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
2081 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY,
2082 Position(nextElement, endOffset), SEL_DEFAULT_AFFINITY));
2089 void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end)
2092 for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) {
2095 if (node && isEmptyStyleSpan(node->previousSibling())) {
2096 removeNodePreservingChildren(node->previousSibling());
2099 if (start.node() == end.node()) {
2100 if (start.node()->isTextNode()) {
2101 for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
2102 if (isEmptyStyleSpan(cur)) {
2103 removeNodePreservingChildren(cur);
2110 if (start.node()->isTextNode()) {
2111 for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) {
2112 if (isEmptyStyleSpan(cur)) {
2113 removeNodePreservingChildren(cur);
2119 if (end.node()->isTextNode()) {
2120 for (NodeImpl *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
2121 if (isEmptyStyleSpan(cur)) {
2122 removeNodePreservingChildren(cur);
2129 for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) {
2131 if (node && isEmptyStyleSpan(node->nextSibling())) {
2132 removeNodePreservingChildren(node->nextSibling());
2136 void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
2142 NodeImpl *node = startNode;
2144 NodeImpl *next = node->traverseNextNode();
2145 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
2147 appendNode(node, element);
2149 if (node == endNode)
2155 void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *node)
2157 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
2162 HTMLElementImpl *block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
2166 StyleChange styleChange(style, Position(block, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
2167 if (styleChange.cssStyle().length() > 0) {
2168 moveParagraphContentsToNewBlockIfNecessary(Position(node, 0));
2169 block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
2170 DOMString cssText = styleChange.cssStyle();
2171 CSSMutableStyleDeclarationImpl *decl = block->inlineStyleDecl();
2173 cssText += decl->cssText();
2174 setNodeAttribute(block, ATTR_STYLE, cssText);
2178 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *startNode, NodeImpl *endNode)
2180 StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
2181 int exceptionCode = 0;
2184 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
2186 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
2187 ElementImpl *fontElement = createFontElement(document());
2188 ASSERT(exceptionCode == 0);
2189 insertNodeBefore(fontElement, startNode);
2190 if (styleChange.applyFontColor())
2191 fontElement->setAttribute(ATTR_COLOR, styleChange.fontColor());
2192 if (styleChange.applyFontFace())
2193 fontElement->setAttribute(ATTR_FACE, styleChange.fontFace());
2194 if (styleChange.applyFontSize())
2195 fontElement->setAttribute(ATTR_SIZE, styleChange.fontSize());
2196 surroundNodeRangeWithElement(startNode, endNode, fontElement);
2199 if (styleChange.cssStyle().length() > 0) {
2200 ElementImpl *styleElement = createStyleSpanElement(document());
2201 styleElement->ref();
2202 styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
2203 insertNodeBefore(styleElement, startNode);
2204 styleElement->deref();
2205 surroundNodeRangeWithElement(startNode, endNode, styleElement);
2208 if (styleChange.applyBold()) {
2209 ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
2210 ASSERT(exceptionCode == 0);
2211 insertNodeBefore(boldElement, startNode);
2212 surroundNodeRangeWithElement(startNode, endNode, boldElement);
2215 if (styleChange.applyItalic()) {
2216 ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
2217 ASSERT(exceptionCode == 0);
2218 insertNodeBefore(italicElement, startNode);
2219 surroundNodeRangeWithElement(startNode, endNode, italicElement);
2223 float ApplyStyleCommand::computedFontSize(const NodeImpl *node)
2230 Position pos(const_cast<NodeImpl *>(node), 0);
2231 CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
2234 computedStyle->ref();
2236 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE));
2239 size = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
2243 computedStyle->deref();
2247 void ApplyStyleCommand::joinChildTextNodes(NodeImpl *node, const Position &start, const Position &end)
2252 Position newStart = start;
2253 Position newEnd = end;
2255 NodeImpl *child = node->firstChild();
2257 NodeImpl *next = child->nextSibling();
2258 if (child->isTextNode() && next && next->isTextNode()) {
2259 TextImpl *childText = static_cast<TextImpl *>(child);
2260 TextImpl *nextText = static_cast<TextImpl *>(next);
2261 if (next == start.node())
2262 newStart = Position(childText, childText->length() + start.offset());
2263 if (next == end.node())
2264 newEnd = Position(childText, childText->length() + end.offset());
2265 DOMString textToMove = nextText->data();
2266 insertTextIntoNode(childText, childText->length(), textToMove);
2268 // don't move child node pointer. it may want to merge with more text nodes.
2271 child = child->nextSibling();
2275 setEndingSelection(Selection(newStart, SEL_DEFAULT_AFFINITY, newEnd, SEL_DEFAULT_AFFINITY));
2278 //------------------------------------------------------------------------------------------
2279 // DeleteFromTextNodeCommand
2281 DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(DocumentImpl *document, TextImpl *node, long offset, long count)
2282 : EditCommand(document), m_node(node), m_offset(offset), m_count(count)
2285 ASSERT(m_offset >= 0);
2286 ASSERT(m_offset < (long)m_node->length());
2287 ASSERT(m_count >= 0);
2292 DeleteFromTextNodeCommand::~DeleteFromTextNodeCommand()
2298 void DeleteFromTextNodeCommand::doApply()
2302 int exceptionCode = 0;
2303 m_text = m_node->substringData(m_offset, m_count, exceptionCode);
2304 ASSERT(exceptionCode == 0);
2306 m_node->deleteData(m_offset, m_count, exceptionCode);
2307 ASSERT(exceptionCode == 0);
2310 void DeleteFromTextNodeCommand::doUnapply()
2313 ASSERT(!m_text.isEmpty());
2315 int exceptionCode = 0;
2316 m_node->insertData(m_offset, m_text, exceptionCode);
2317 ASSERT(exceptionCode == 0);
2320 //------------------------------------------------------------------------------------------
2321 // DeleteSelectionCommand
2323 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, bool smartDelete, bool mergeBlocksAfterDelete)
2324 : CompositeEditCommand(document),
2325 m_hasSelectionToDelete(false),
2326 m_smartDelete(smartDelete),
2327 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
2335 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
2336 : CompositeEditCommand(document),
2337 m_hasSelectionToDelete(true),
2338 m_smartDelete(smartDelete),
2339 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
2340 m_selectionToDelete(selection),
2348 void DeleteSelectionCommand::initializePositionData()
2351 // Handle setting some basic positions
2353 Position start = m_selectionToDelete.start();
2354 Position end = m_selectionToDelete.end();
2356 m_upstreamStart = start.upstream(StayInBlock);
2357 m_downstreamStart = start.downstream(StayInBlock);
2358 m_upstreamEnd = end.upstream(StayInBlock);
2359 m_downstreamEnd = end.downstream(StayInBlock);
2362 // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
2364 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity());
2365 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
2367 if (m_smartDelete) {
2368 bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity(), true).isNotNull();
2369 if (hasLeadingWhitespaceBeforeAdjustment) {
2370 VisiblePosition visiblePos = VisiblePosition(start, m_selectionToDelete.startAffinity()).previous();
2371 Position pos = visiblePos.deepEquivalent();
2372 // Expand out one character upstream for smart delete and recalculate
2373 // positions based on this change.
2374 m_upstreamStart = pos.upstream(StayInBlock);
2375 m_downstreamStart = pos.downstream(StayInBlock);
2376 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
2378 // Note: trailing whitespace is only considered for smart delete if there is no leading
2379 // whitespace, as in the case where you double-click the first word of a paragraph.
2380 if (!hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
2381 // Expand out one character downstream for smart delete and recalculate
2382 // positions based on this change.
2383 Position pos = VisiblePosition(end, m_selectionToDelete.endAffinity()).next().deepEquivalent();
2384 m_upstreamEnd = pos.upstream(StayInBlock);
2385 m_downstreamEnd = pos.downstream(StayInBlock);
2386 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
2390 m_trailingWhitespaceValid = true;
2393 // Handle setting start and end blocks and the start node.
2395 m_startBlock = m_downstreamStart.node()->enclosingBlockFlowElement();
2396 m_startBlock->ref();
2397 m_endBlock = m_upstreamEnd.node()->enclosingBlockFlowElement();
2399 m_startNode = m_upstreamStart.node();
2403 // Handle detecting if the line containing the selection end is itself fully selected.
2404 // This is one of the tests that determines if block merging of content needs to be done.
2406 VisiblePosition visibleEnd(end, m_selectionToDelete.endAffinity());
2407 if (isFirstVisiblePositionInParagraph(visibleEnd) || isLastVisiblePositionInParagraph(visibleEnd)) {
2408 Position previousLineStart = previousLinePosition(visibleEnd, 0).deepEquivalent();
2409 if (previousLineStart.isNull() || RangeImpl::compareBoundaryPoints(previousLineStart, m_downstreamStart) >= 0)
2410 m_mergeBlocksAfterDelete = false;
2413 debugPosition("m_upstreamStart ", m_upstreamStart);
2414 debugPosition("m_downstreamStart ", m_downstreamStart);
2415 debugPosition("m_upstreamEnd ", m_upstreamEnd);
2416 debugPosition("m_downstreamEnd ", m_downstreamEnd);
2417 debugPosition("m_leadingWhitespace ", m_leadingWhitespace);
2418 debugPosition("m_trailingWhitespace ", m_trailingWhitespace);
2419 debugNode( "m_startBlock ", m_startBlock);
2420 debugNode( "m_endBlock ", m_endBlock);
2421 debugNode( "m_startNode ", m_startNode);
2424 void DeleteSelectionCommand::insertPlaceholderForAncestorBlockContent()
2426 // This code makes sure a line does not disappear when deleting in this case:
2427 // <p>foo</p>bar<p>baz</p>
2428 // Select "bar" and hit delete. If nothing is done, the line containing bar will disappear.
2429 // It needs to be held open by inserting a placeholder.
2431 // <rdar://problem/3928305> selecting an entire line and typing over causes new inserted text at top of document
2433 // The checks below detect the case where the selection contains content in an ancestor block
2434 // surrounded by child blocks.
2436 NodeImpl *upstreamBlock = m_upstreamStart.node()->enclosingBlockFlowElement();
2437 NodeImpl *beforeUpstreamBlock = m_upstreamStart.upstream().node()->enclosingBlockFlowElement();
2439 if (upstreamBlock != beforeUpstreamBlock && beforeUpstreamBlock->isAncestor(upstreamBlock)) {
2440 NodeImpl *downstreamBlock = m_downstreamEnd.node()->enclosingBlockFlowElement();
2441 NodeImpl *afterDownstreamBlock = m_downstreamEnd.downstream().node()->enclosingBlockFlowElement();
2443 if (afterDownstreamBlock != downstreamBlock && afterDownstreamBlock != upstreamBlock) {
2444 NodeImpl *block = createDefaultParagraphElement(document());
2445 insertNodeBefore(block, m_upstreamStart.node());
2446 insertBlockPlaceholderIfNeeded(block);
2447 m_endingPosition = Position(block, 0);
2452 void DeleteSelectionCommand::saveTypingStyleState()
2454 // Figure out the typing style in effect before the delete is done.
2455 // FIXME: Improve typing style.
2456 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2457 CSSComputedStyleDeclarationImpl *computedStyle = m_selectionToDelete.start().computedStyle();
2458 computedStyle->ref();
2459 m_typingStyle = computedStyle->copyInheritableProperties();
2460 m_typingStyle->ref();
2461 computedStyle->deref();
2464 bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
2466 // Check for special-case where the selection contains only a BR on a line by itself after another BR.
2467 bool upstreamStartIsBR = m_startNode->id() == ID_BR;
2468 bool downstreamStartIsBR = m_downstreamStart.node()->id() == ID_BR;
2469 bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
2470 if (isBROnLineByItself) {
2471 removeNode(m_downstreamStart.node());
2472 m_endingPosition = m_upstreamStart;
2473 m_mergeBlocksAfterDelete = false;
2477 // Check for special-case where the selection contains only a BR right after a block ended.
2478 bool downstreamEndIsBR = m_downstreamEnd.node()->id() == ID_BR;
2479 Position upstreamFromBR = m_downstreamEnd.upstream();
2480 Position downstreamFromStart = m_downstreamStart.downstream();
2481 bool startIsBRAfterBlock = downstreamEndIsBR && downstreamFromStart.node() == m_downstreamEnd.node() &&
2482 m_downstreamEnd.node()->enclosingBlockFlowElement() != upstreamFromBR.node()->enclosingBlockFlowElement();
2483 if (startIsBRAfterBlock) {
2484 removeNode(m_downstreamEnd.node());
2485 m_endingPosition = upstreamFromBR;
2486 m_mergeBlocksAfterDelete = false;
2490 // Not a special-case delete per se, but we can detect that the merging of content between blocks
2491 // should not be done.
2492 if (upstreamStartIsBR && downstreamStartIsBR)
2493 m_mergeBlocksAfterDelete = false;
2498 void DeleteSelectionCommand::setStartNode(NodeImpl *node)
2500 NodeImpl *old = m_startNode;
2508 void DeleteSelectionCommand::handleGeneralDelete()
2510 int startOffset = m_upstreamStart.offset();
2511 VisiblePosition visibleEnd = VisiblePosition(m_downstreamEnd, m_selectionToDelete.endAffinity());
2512 bool endAtEndOfBlock = isEndOfBlock(visibleEnd);
2514 // Handle some special cases where the selection begins and ends on specific visible units.
2515 // Sometimes a node that is actually selected needs to be retained in order to maintain
2516 // user expectations for the delete operation. Here is an example:
2517 // 1. Open a new Blot or Mail document
2518 // 2. hit Return ten times or so
2519 // 3. Type a letter (do not hit Return after it)
2520 // 4. Type shift-up-arrow to select the line containing the letter and the previous blank line
2522 // You expect the insertion point to wind up at the start of the line where your selection began.
2523 // Because of the nature of HTML, the editing code needs to perform a special check to get
2524 // this behavior. So:
2525 // If the entire start block is selected, and the selection does not extend to the end of the
2526 // end of a block other than the block containing the selection start, then do not delete the
2527 // start block, otherwise delete the start block.
2528 // A similar case is provided to cover selections starting in BR elements.
2529 if (startOffset == 1 && m_startNode->id() == ID_BR) {
2530 setStartNode(m_startNode->traverseNextNode());
2533 if (m_startBlock != m_endBlock && startOffset == 0 && m_startNode->id() == ID_BR && endAtEndOfBlock) {
2534 // Don't delete the BR element
2535 setStartNode(m_startNode->traverseNextNode());
2537 else if (m_startBlock != m_endBlock && isStartOfBlock(VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()))) {
2538 if (!isStartOfBlock(visibleEnd) && endAtEndOfBlock) {
2539 // Delete all the children of the block, but not the block itself.
2540 setStartNode(m_startBlock->firstChild());
2544 else if (startOffset >= m_startNode->caretMaxOffset()) {
2545 // Move the start node to the next node in the tree since the startOffset is equal to
2546 // or beyond the start node's caretMaxOffset This means there is nothing visible to delete.
2547 // However, before moving on, delete any insignificant text that may be present in a text node.
2548 if (m_startNode->isTextNode()) {
2549 // Delete any insignificant text from this node.
2550 TextImpl *text = static_cast<TextImpl *>(m_startNode);
2551 if (text->length() > (unsigned)m_startNode->caretMaxOffset())
2552 deleteTextFromNode(text, m_startNode->caretMaxOffset(), text->length() - m_startNode->caretMaxOffset());
2555 // shift the start node to the next
2556 setStartNode(m_startNode->traverseNextNode());
2560 if (m_startNode == m_downstreamEnd.node()) {
2561 // The selection to delete is all in one node.
2562 if (!m_startNode->renderer() ||
2563 (startOffset <= m_startNode->caretMinOffset() && m_downstreamEnd.offset() >= m_startNode->caretMaxOffset())) {
2565 removeFullySelectedNode(m_startNode);
2567 else if (m_downstreamEnd.offset() - startOffset > 0) {
2568 // in a text node that needs to be trimmed
2569 TextImpl *text = static_cast<TextImpl *>(m_startNode);
2570 deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset);
2571 m_trailingWhitespaceValid = false;
2575 // The selection to delete spans more than one node.
2576 NodeImpl *node = m_startNode;
2578 if (startOffset > 0) {
2579 // in a text node that needs to be trimmed
2580 TextImpl *text = static_cast<TextImpl *>(node);
2581 deleteTextFromNode(text, startOffset, text->length() - startOffset);
2582 node = node->traverseNextNode();
2585 // handle deleting all nodes that are completely selected
2586 while (node && node != m_downstreamEnd.node()) {
2587 if (!m_downstreamEnd.node()->isAncestor(node)) {
2588 NodeImpl *nextNode = node->traverseNextSibling();
2589 removeFullySelectedNode(node);
2593 NodeImpl *n = node->lastChild();
2594 while (n && n->lastChild())
2596 if (n == m_downstreamEnd.node() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMaxOffset()) {
2597 // remove an ancestor of m_downstreamEnd.node(), and thus m_downstreamEnd.node() itself
2598 removeFullySelectedNode(node);
2599 m_trailingWhitespaceValid = false;
2603 node = node->traverseNextNode();
2608 if (m_downstreamEnd.node() != m_startNode && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMinOffset()) {
2609 if (m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMaxOffset()) {
2610 // need to delete whole node
2611 // we can get here if this is the last node in the block
2612 removeFullySelectedNode(m_downstreamEnd.node());
2613 m_trailingWhitespaceValid = false;
2616 // in a text node that needs to be trimmed
2617 TextImpl *text = static_cast<TextImpl *>(m_downstreamEnd.node());
2618 if (m_downstreamEnd.offset() > 0) {
2619 deleteTextFromNode(text, 0, m_downstreamEnd.offset());
2620 m_downstreamEnd = Position(text, 0);
2621 m_trailingWhitespaceValid = false;
2628 void DeleteSelectionCommand::fixupWhitespace()
2630 document()->updateLayout();
2631 if (m_leadingWhitespace.isNotNull() && (m_trailingWhitespace.isNotNull() || !m_leadingWhitespace.isRenderedCharacter())) {
2632 LOG(Editing, "replace leading");
2633 TextImpl *textNode = static_cast<TextImpl *>(m_leadingWhitespace.node());
2634 replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
2636 else if (m_trailingWhitespace.isNotNull()) {
2637 if (m_trailingWhitespaceValid) {
2638 if (!m_trailingWhitespace.isRenderedCharacter()) {
2639 LOG(Editing, "replace trailing [valid]");
2640 TextImpl *textNode = static_cast<TextImpl *>(m_trailingWhitespace.node());
2641 replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
2645 Position pos = m_endingPosition.downstream(StayInBlock);
2646 pos = Position(pos.node(), pos.offset() - 1);
2647 if (isWS(pos) && !pos.isRenderedCharacter()) {
2648 LOG(Editing, "replace trailing [invalid]");
2649 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
2650 replaceTextInNode(textNode, pos.offset(), 1, nonBreakingSpaceString());
2651 // need to adjust ending position since the trailing position is not valid.
2652 m_endingPosition = pos;
2658 // This function moves nodes in the block containing startNode to dstBlock, starting
2659 // from startNode and proceeding to the end of the paragraph. Nodes in the block containing
2660 // startNode that appear in document order before startNode are not moved.
2661 // This function is an important helper for deleting selections that cross paragraph
2663 void DeleteSelectionCommand::moveNodesAfterNode()
2665 if (!m_mergeBlocksAfterDelete)
2668 if (m_endBlock == m_startBlock)
2671 NodeImpl *startNode = m_downstreamEnd.node();
2672 NodeImpl *dstNode = m_upstreamStart.node();
2674 if (!startNode->inDocument() || !dstNode->inDocument())
2677 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
2678 if (isTableStructureNode(startBlock))
2679 // Do not move content between parts of a table
2682 // Now that we are about to add content, check to see if a placeholder element
2684 removeBlockPlaceholderIfNeeded(startBlock);
2686 // Move the subtree containing node
2687 NodeImpl *node = startNode->enclosingInlineElement();
2689 // Insert after the subtree containing destNode
2690 NodeImpl *refNode = dstNode->enclosingInlineElement();
2692 // Nothing to do if start is already at the beginning of dstBlock
2693 NodeImpl *dstBlock = refNode->enclosingBlockFlowElement();
2694 if (startBlock == dstBlock->firstChild())
2698 NodeImpl *rootNode = refNode->rootEditableElement();
2699 while (node && node->isAncestor(startBlock)) {
2700 NodeImpl *moveNode = node;
2701 node = node->nextSibling();
2702 removeNode(moveNode);
2703 if (moveNode->id() == ID_BR && !moveNode->renderer()) {
2704 // Just remove this node, and don't put it back.
2705 // If the BR was not rendered (since it was at the end of a block, for instance),
2706 // putting it back in the document might make it appear, and that is not desirable.
2709 if (refNode == rootNode)
2710 insertNodeAt(moveNode, refNode, 0);
2712 insertNodeAfter(moveNode, refNode);
2714 if (moveNode->id() == ID_BR)
2718 // If the startBlock no longer has any kids, we may need to deal with adding a BR
2719 // to make the layout come out right. Consider this document:
2725 // Placing the insertion before before the 'T' of 'Two' and hitting delete will
2726 // move the contents of the div to the block containing 'One' and delete the div.
2727 // This will have the side effect of moving 'Three' on to the same line as 'One'
2728 // and 'Two'. This is undesirable. We fix this up by adding a BR before the 'Three'.
2729 // This may not be ideal, but it is better than nothing.
2730 document()->updateLayout();
2731 if (!startBlock->renderer() || !startBlock->renderer()->firstChild()) {
2732 removeNode(startBlock);
2733 document()->updateLayout();
2734 if (refNode->renderer() && refNode->renderer()->inlineBox() && refNode->renderer()->inlineBox()->nextOnLineExists()) {
2735 insertNodeAfter(createBreakElement(document()), refNode);
2740 void DeleteSelectionCommand::calculateEndingPosition()
2742 if (m_endingPosition.isNotNull() && m_endingPosition.node()->inDocument())
2745 m_endingPosition = m_upstreamStart;
2746 if (m_endingPosition.node()->inDocument())
2749 m_endingPosition = m_downstreamEnd;
2750 if (m_endingPosition.node()->inDocument())
2753 m_endingPosition = Position(m_startBlock, 0);
2754 if (m_endingPosition.node()->inDocument())
2757 m_endingPosition = Position(m_endBlock, 0);
2758 if (m_endingPosition.node()->inDocument())
2761 m_endingPosition = Position(document()->documentElement(), 0);
2764 void DeleteSelectionCommand::calculateTypingStyleAfterDelete(bool insertedPlaceholder)
2766 // Compute the difference between the style before the delete and the style now
2767 // after the delete has been done. Set this style on the part, so other editing
2768 // commands being composed with this one will work, and also cache it on the command,
2769 // so the KHTMLPart::appliedEditing can set it after the whole composite command
2771 // FIXME: Improve typing style.
2772 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2773 CSSComputedStyleDeclarationImpl endingStyle(m_endingPosition.node());
2774 endingStyle.diff(m_typingStyle);
2775 if (!m_typingStyle->length()) {
2776 m_typingStyle->deref();
2779 if (insertedPlaceholder && m_typingStyle) {
2780 // Apply style to the placeholder. This makes sure that the single line in the
2781 // paragraph has the right height, and that the paragraph takes on the style
2782 // of the preceding line and retains it even if you click away, click back, and
2783 // then start typing. In this case, the typing style is applied right now, and
2784 // is not retained until the next typing action.
2785 Position pastPlaceholder = endOfParagraph(VisiblePosition(m_endingPosition, m_selectionToDelete.endAffinity())).deepEquivalent();
2786 setEndingSelection(Selection(m_endingPosition, m_selectionToDelete.endAffinity(), pastPlaceholder, DOWNSTREAM));
2787 applyStyle(m_typingStyle, EditActionUnspecified);
2788 m_typingStyle->deref();
2791 // Set m_typingStyle as the typing style.
2792 // It's perfectly OK for m_typingStyle to be null.
2793 document()->part()->setTypingStyle(m_typingStyle);
2794 setTypingStyle(m_typingStyle);
2797 void DeleteSelectionCommand::clearTransientState()
2799 m_selectionToDelete.clear();
2800 m_upstreamStart.clear();
2801 m_downstreamStart.clear();
2802 m_upstreamEnd.clear();
2803 m_downstreamEnd.clear();
2804 m_endingPosition.clear();
2805 m_leadingWhitespace.clear();
2806 m_trailingWhitespace.clear();
2809 m_startBlock->deref();
2813 m_endBlock->deref();
2817 m_startNode->deref();
2820 if (m_typingStyle) {
2821 m_typingStyle->deref();
2826 void DeleteSelectionCommand::doApply()
2828 // If selection has not been set to a custom selection when the command was created,
2829 // use the current ending selection.
2830 if (!m_hasSelectionToDelete)
2831 m_selectionToDelete = endingSelection();
2833 if (!m_selectionToDelete.isRange())
2836 // save this to later make the selection with
2837 EAffinity affinity = m_selectionToDelete.startAffinity();
2840 initializePositionData();
2842 if (!m_startBlock || !m_endBlock) {
2843 // Can't figure out what blocks we're in. This can happen if
2844 // the document structure is not what we are expecting, like if
2845 // the document has no body element, or if the editable block
2846 // has been changed to display: inline. Some day it might
2847 // be nice to be able to deal with this, but for now, bail.
2848 clearTransientState();
2852 // Delete any text that may hinder our ability to fixup whitespace after the detele
2853 deleteInsignificantTextDownstream(m_trailingWhitespace);
2855 saveTypingStyleState();
2856 insertPlaceholderForAncestorBlockContent();
2858 if (!handleSpecialCaseBRDelete())
2859 handleGeneralDelete();
2861 // Do block merge if start and end of selection are in different blocks.
2862 moveNodesAfterNode();
2864 calculateEndingPosition();
2867 // If the delete emptied a block, add in a placeholder so the block does not
2868 // seem to disappear.
2869 bool insertedPlaceholder = insertBlockPlaceholderIfNeeded(m_endingPosition.node());
2870 calculateTypingStyleAfterDelete(insertedPlaceholder);
2871 debugPosition("endingPosition ", m_endingPosition);
2872 setEndingSelection(Selection(m_endingPosition, affinity));
2873 clearTransientState();
2874 rebalanceWhitespace();
2877 EditAction DeleteSelectionCommand::editingAction() const
2879 // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
2880 // but in that case there's a TypingCommand that supplies the editingAction(), so
2881 // the Undo menu correctly shows "Undo Typing"
2882 return EditActionCut;
2885 bool DeleteSelectionCommand::preservesTypingStyle() const
2890 //------------------------------------------------------------------------------------------
2891 // InsertIntoTextNode
2893 InsertIntoTextNode::InsertIntoTextNode(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
2894 : EditCommand(document), m_node(node), m_offset(offset)
2897 ASSERT(m_offset >= 0);
2898 ASSERT(!text.isEmpty());
2901 m_text = text.copy(); // make a copy to ensure that the string never changes
2904 InsertIntoTextNode::~InsertIntoTextNode()
2910 void InsertIntoTextNode::doApply()
2913 ASSERT(m_offset >= 0);
2914 ASSERT(!m_text.isEmpty());
2916 int exceptionCode = 0;
2917 m_node->insertData(m_offset, m_text, exceptionCode);
2918 ASSERT(exceptionCode == 0);
2921 void InsertIntoTextNode::doUnapply()
2924 ASSERT(m_offset >= 0);
2925 ASSERT(!m_text.isEmpty());
2927 int exceptionCode = 0;
2928 m_node->deleteData(m_offset, m_text.length(), exceptionCode);
2929 ASSERT(exceptionCode == 0);
2932 //------------------------------------------------------------------------------------------
2933 // InsertLineBreakCommand
2935 InsertLineBreakCommand::InsertLineBreakCommand(DocumentImpl *document)
2936 : CompositeEditCommand(document)
2940 bool InsertLineBreakCommand::preservesTypingStyle() const
2945 void InsertLineBreakCommand::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
2947 // Insert the BR after the caret position. In the case the
2948 // position is a block, do an append. We don't want to insert
2949 // the BR *after* the block.
2950 Position upstream(pos.upstream(StayInBlock));
2951 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
2952 if (cb == pos.node())
2953 appendNode(node, cb);
2955 insertNodeAfter(node, pos.node());
2958 void InsertLineBreakCommand::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
2960 // Insert the BR after the caret position. In the case the
2961 // position is a block, do an append. We don't want to insert
2962 // the BR *before* the block.
2963 Position upstream(pos.upstream(StayInBlock));
2964 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
2965 if (cb == pos.node())
2966 appendNode(node, cb);
2968 insertNodeBefore(node, pos.node());
2971 void InsertLineBreakCommand::doApply()
2974 Selection selection = endingSelection();
2976 ElementImpl *breakNode = createBreakElement(document());
2977 NodeImpl *nodeToInsert = breakNode;
2979 Position pos(selection.start().upstream(StayInBlock));
2980 bool atStart = pos.offset() <= pos.node()->caretMinOffset();
2981 bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
2982 bool atEndOfBlock = isLastVisiblePositionInBlock(VisiblePosition(pos, selection.startAffinity()));
2985 LOG(Editing, "input newline case 1");
2986 // Check for a trailing BR. If there isn't one, we'll need to insert an "extra" one.
2987 // This makes the "real" BR we want to insert appear in the rendering without any
2988 // significant side effects (and no real worries either since you can't arrow past
2990 if (pos.node()->id() == ID_BR && pos.offset() == 0) {
2991 // Already placed in a trailing BR. Insert "real" BR before it and leave the selection alone.
2992 insertNodeBefore(nodeToInsert, pos.node());
2995 NodeImpl *next = pos.node()->traverseNextNode();
2996 bool hasTrailingBR = next && next->id() == ID_BR && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
2997 insertNodeAfterPosition(nodeToInsert, pos);
2998 if (hasTrailingBR) {
2999 setEndingSelection(Selection(Position(next, 0), DOWNSTREAM));
3001 else if (!document()->inStrictMode()) {
3002 // Insert an "extra" BR at the end of the block.
3003 ElementImpl *extraBreakNode = createBreakElement(document());
3004 insertNodeAfter(extraBreakNode, nodeToInsert);
3005 setEndingSelection(Position(extraBreakNode, 0), DOWNSTREAM);
3010 LOG(Editing, "input newline case 2");
3011 // Insert node before downstream position, and place caret there as well.
3012 Position endingPosition = pos.downstream(StayInBlock);
3013 insertNodeBeforePosition(nodeToInsert, endingPosition);
3014 setEndingSelection(endingPosition, DOWNSTREAM);
3017 LOG(Editing, "input newline case 3");
3018 // Insert BR after this node. Place caret in the position that is downstream
3019 // of the current position, reckoned before inserting the BR in between.
3020 Position endingPosition = pos.downstream(StayInBlock);
3021 insertNodeAfterPosition(nodeToInsert, pos);
3022 setEndingSelection(endingPosition, DOWNSTREAM);
3025 // Split a text node
3026 LOG(Editing, "input newline case 4");
3027 ASSERT(pos.node()->isTextNode());
3030 int exceptionCode = 0;
3031 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
3032 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
3033 deleteTextFromNode(textNode, 0, pos.offset());
3034 insertNodeBefore(textBeforeNode, textNode);
3035 insertNodeBefore(nodeToInsert, textNode);
3036 Position endingPosition = Position(textNode, 0);
3038 // Handle whitespace that occurs after the split
3039 document()->updateLayout();
3040 if (!endingPosition.isRenderedCharacter()) {
3041 // Clear out all whitespace and insert one non-breaking space
3042 deleteInsignificantTextDownstream(endingPosition);
3043 insertTextIntoNode(textNode, 0, nonBreakingSpaceString());
3046 setEndingSelection(endingPosition, DOWNSTREAM);
3049 // Handle the case where there is a typing style.
3050 // FIXME: Improve typing style.
3051 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3053 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
3055 if (typingStyle && typingStyle->length() > 0) {
3056 Selection selectionBeforeStyle = endingSelection();
3058 DOM::RangeImpl *rangeAroundNode = document()->createRange();
3060 rangeAroundNode->selectNode(nodeToInsert, exception);
3062 // affinity is not really important since this is a temp selection
3063 // just for calling applyStyle
3064 setEndingSelection(Selection(rangeAroundNode, khtml::SEL_DEFAULT_AFFINITY, khtml::SEL_DEFAULT_AFFINITY));
3065 applyStyle(typingStyle);
3067 setEndingSelection(selectionBeforeStyle);
3070 rebalanceWhitespace();
3073 //------------------------------------------------------------------------------------------
3074 // InsertNodeBeforeCommand
3076 InsertNodeBeforeCommand::InsertNodeBeforeCommand(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
3077 : EditCommand(document), m_insertChild(insertChild), m_refChild(refChild)
3079 ASSERT(m_insertChild);
3080 m_insertChild->ref();
3086 InsertNodeBeforeCommand::~InsertNodeBeforeCommand()
3088 ASSERT(m_insertChild);
3089 m_insertChild->deref();
3092 m_refChild->deref();
3095 void InsertNodeBeforeCommand::doApply()
3097 ASSERT(m_insertChild);
3099 ASSERT(m_refChild->parentNode());
3101 int exceptionCode = 0;
3102 m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
3103 ASSERT(exceptionCode == 0);
3106 void InsertNodeBeforeCommand::doUnapply()
3108 ASSERT(m_insertChild);
3110 ASSERT(m_refChild->parentNode());
3112 int exceptionCode = 0;
3113 m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
3114 ASSERT(exceptionCode == 0);
3117 //------------------------------------------------------------------------------------------
3118 // InsertParagraphSeparatorCommand
3120 InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(DocumentImpl *document)
3121 : CompositeEditCommand(document), m_style(0)
3125 InsertParagraphSeparatorCommand::~InsertParagraphSeparatorCommand()
3127 derefNodesInList(clonedNodes);
3132 bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
3137 ElementImpl *InsertParagraphSeparatorCommand::createParagraphElement()
3139 ElementImpl *element = createDefaultParagraphElement(document());
3141 clonedNodes.append(element);
3145 void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
3147 // It is only important to set a style to apply later if we're at the boundaries of
3148 // a paragraph. Otherwise, content that is moved as part of the work of the command
3149 // will lend their styles to the new paragraph without any extra work needed.
3150 VisiblePosition visiblePos(pos, UPSTREAM);
3151 if (!isFirstVisiblePositionInParagraph(visiblePos) && !isLastVisiblePositionInParagraph(visiblePos))
3156 m_style = styleAtPosition(pos);
3160 void InsertParagraphSeparatorCommand::applyStyleAfterInsertion()
3162 // FIXME: Improve typing style.
3163 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3167 CSSComputedStyleDeclarationImpl endingStyle(endingSelection().start().node());
3168 endingStyle.diff(m_style);
3169 if (m_style->length() > 0) {
3170 applyStyle(m_style);
3174 void InsertParagraphSeparatorCommand::doApply()
3176 bool splitText = false;
3177 Selection selection = endingSelection();
3178 if (selection.isNone())
3181 // Delete the current selection.
3182 // If the selection is a range and the start and end nodes are in different blocks,
3183 // then this command bails after the delete, but takes the one additional step of
3184 // moving the selection downstream so it is in the ending block (if that block is
3185 // still around, that is).
3186 Position pos = selection.start();
3187 EAffinity affinity = selection.startAffinity();
3189 if (selection.isRange()) {
3190 NodeImpl *startBlockBeforeDelete = selection.start().node()->enclosingBlockFlowElement();
3191 NodeImpl *endBlockBeforeDelete = selection.end().node()->enclosingBlockFlowElement();
3192 bool doneAfterDelete = startBlockBeforeDelete != endBlockBeforeDelete;
3193 calculateStyleBeforeInsertion(pos);
3194 deleteSelection(false, false);
3195 if (doneAfterDelete) {
3196 document()->updateLayout();
3197 setEndingSelection(endingSelection().start().downstream(), DOWNSTREAM);
3198 rebalanceWhitespace();
3199 applyStyleAfterInsertion();
3202 pos = endingSelection().start();
3203 affinity = endingSelection().startAffinity();
3206 calculateStyleBeforeInsertion(pos);
3208 // Find the start block.
3209 NodeImpl *startNode = pos.node();
3210 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
3211 if (!startBlock || !startBlock->parentNode())
3214 VisiblePosition visiblePos(pos, affinity);
3215 bool isFirstInBlock = isFirstVisiblePositionInBlock(visiblePos);
3216 bool isLastInBlock = isLastVisiblePositionInBlock(visiblePos);
3217 bool startBlockIsRoot = startBlock == startBlock->rootEditableElement();
3219 // This is the block that is going to be inserted.
3220 NodeImpl *blockToInsert = startBlockIsRoot ? createParagraphElement() : startBlock->cloneNode(false);
3222 //---------------------------------------------------------------------
3223 // Handle empty block case.
3224 if (isFirstInBlock && isLastInBlock) {
3225 LOG(Editing, "insert paragraph separator: empty block case");
3226 if (startBlockIsRoot) {
3227 NodeImpl *extraBlock = createParagraphElement();
3228 appendNode(extraBlock, startBlock);
3229 insertBlockPlaceholder(extraBlock);
3230 appendNode(blockToInsert, startBlock);
3233 insertNodeAfter(blockToInsert, startBlock);
3235 insertBlockPlaceholder(blockToInsert);
3236 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
3237 applyStyleAfterInsertion();
3241 //---------------------------------------------------------------------
3242 // Handle case when position is in the first visible position in its block.
3243 // and similar case where upstream position is in another block.
3244 bool upstreamInDifferentBlock = startBlock != pos.upstream(DoNotStayInBlock).node()->enclosingBlockFlowElement();
3245 if (upstreamInDifferentBlock || isFirstInBlock) {
3246 LOG(Editing, "insert paragraph separator: first in block case");
3247 pos = pos.downstream(StayInBlock);
3248 NodeImpl *refNode = isFirstInBlock && !startBlockIsRoot ? startBlock : pos.node();
3249 insertNodeBefore(blockToInsert, refNode);
3250 insertBlockPlaceholder(blockToInsert);
3251 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
3252 applyStyleAfterInsertion();
3253 setEndingSelection(pos, DOWNSTREAM);
3257 //---------------------------------------------------------------------
3258 // Handle case when position is in the last visible position in its block.
3259 if (isLastInBlock) {
3260 LOG(Editing, "insert paragraph separator: last in block case");
3261 if (startBlockIsRoot)
3262 appendNode(blockToInsert, startBlock);
3264 insertNodeAfter(blockToInsert, startBlock);
3265 insertBlockPlaceholder(blockToInsert);
3266 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
3267 applyStyleAfterInsertion();
3271 //---------------------------------------------------------------------
3272 // Handle the (more complicated) general case,
3274 LOG(Editing, "insert paragraph separator: general case");
3276 // Check if pos.node() is a <br>. If it is, and the document is in quirks mode,
3277 // then this <br> will collapse away when we add a block after it. Add an extra <br>.
3278 if (!document()->inStrictMode()) {
3279 Position upstreamPos = pos.upstream(StayInBlock);
3280 if (upstreamPos.node()->id() == ID_BR)
3281 insertNodeAfter(createBreakElement(document()), upstreamPos.node());
3284 // Move downstream. Typing style code will take care of carrying along the
3285 // style of the upstream position.
3286 pos = pos.downstream(StayInBlock);
3287 startNode = pos.node();
3289 // Build up list of ancestors in between the start node and the start block.
3290 if (startNode != startBlock) {
3291 for (NodeImpl *n = startNode->parentNode(); n && n != startBlock; n = n->parentNode())
3292 ancestors.prepend(n);
3295 // Make sure we do not cause a rendered space to become unrendered.
3296 // FIXME: We need the affinity for pos, but pos.downstream(StayInBlock) does not give it
3297 Position leadingWhitespace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
3298 if (leadingWhitespace.isNotNull()) {
3299 TextImpl *textNode = static_cast<TextImpl *>(leadingWhitespace.node());
3300 replaceTextInNode(textNode, leadingWhitespace.offset(), 1, nonBreakingSpaceString());
3303 // Split at pos if in the middle of a text node.
3304 if (startNode->isTextNode()) {
3305 TextImpl *textNode = static_cast<TextImpl *>(startNode);
3306 bool atEnd = (unsigned long)pos.offset() >= textNode->length();
3307 if (pos.offset() > 0 && !atEnd) {
3308 splitTextNode(textNode, pos.offset());
3309 pos = Position(startNode, 0);
3314 // Put the added block in the tree.
3315 if (startBlockIsRoot) {
3316 appendNode(blockToInsert, startBlock);
3318 insertNodeAfter(blockToInsert, startBlock);
3321 // Make clones of ancestors in between the start node and the start block.
3322 NodeImpl *parent = blockToInsert;
3323 for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
3324 NodeImpl *child = it.current()->cloneNode(false); // shallow clone
3326 clonedNodes.append(child);
3327 appendNode(child, parent);
3331 // Insert a block placeholder in this case because we know that ther will be no content
3332 // on the first line of the new block before the first block child of the new block.
3333 // So, we need the placeholder to "hold the first line open".
3334 if (startBlock != pos.downstream(DoNotStayInBlock).node()->enclosingBlockFlowElement())
3335 insertBlockPlaceholder(blockToInsert);
3337 // Move the start node and the siblings of the start node.
3338 if (startNode != startBlock) {
3339 NodeImpl *n = startNode;
3340 if (pos.offset() >= startNode->caretMaxOffset()) {
3341 n = startNode->nextSibling();
3343 while (n && n != blockToInsert) {
3344 NodeImpl *next = n->nextSibling();
3346 appendNode(n, parent);
3351 // Move everything after the start node.
3352 NodeImpl *leftParent = ancestors.last();
3353 while (leftParent && leftParent != startBlock) {
3354 parent = parent->parentNode();
3355 NodeImpl *n = leftParent->nextSibling();
3356 while (n && n != blockToInsert) {
3357 NodeImpl *next = n->nextSibling();
3359 appendNode(n, parent);
3362 leftParent = leftParent->parentNode();
3365 // Handle whitespace that occurs after the split
3367 document()->updateLayout();
3368 pos = Position(startNode, 0);
3369 if (!pos.isRenderedCharacter()) {
3370 // Clear out all whitespace and insert one non-breaking space
3371 ASSERT(startNode && startNode->isTextNode());
3372 deleteInsignificantTextDownstream(pos);
3373 insertTextIntoNode(static_cast<TextImpl *>(startNode), 0, nonBreakingSpaceString());
3377 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
3378 rebalanceWhitespace();
3379 applyStyleAfterInsertion();
3382 //------------------------------------------------------------------------------------------
3383 // InsertParagraphSeparatorInQuotedContentCommand
3385 InsertParagraphSeparatorInQuotedContentCommand::InsertParagraphSeparatorInQuotedContentCommand(DocumentImpl *document)
3386 : CompositeEditCommand(document), m_breakNode(0)
3390 InsertParagraphSeparatorInQuotedContentCommand::~InsertParagraphSeparatorInQuotedContentCommand()
3392 derefNodesInList(clonedNodes);
3394 m_breakNode->deref();
3397 void InsertParagraphSeparatorInQuotedContentCommand::doApply()
3399 Selection selection = endingSelection();
3400 if (selection.isNone())
3403 // Delete the current selection.
3404 Position pos = selection.start();
3405 EAffinity affinity = selection.startAffinity();
3406 if (selection.isRange()) {
3407 deleteSelection(false, false);
3408 pos = endingSelection().start().upstream();
3409 affinity = endingSelection().startAffinity();
3412 // Find the top-most blockquote from the start.
3413 NodeImpl *startNode = pos.node();
3414 NodeImpl *topBlockquote = 0;
3415 for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
3416 if (isMailBlockquote(n))
3419 if (!topBlockquote || !topBlockquote->parentNode())
3422 // Insert a break after the top blockquote.
3423 m_breakNode = createBreakElement(document());
3425 insertNodeAfter(m_breakNode, topBlockquote);
3427 if (!isLastVisiblePositionInNode(VisiblePosition(pos, affinity), topBlockquote)) {
3429 NodeImpl *newStartNode = 0;
3430 // Split at pos if in the middle of a text node.
3431 if (startNode->isTextNode()) {
3432 TextImpl *textNode = static_cast<TextImpl *>(startNode);
3433 bool atEnd = (unsigned long)pos.offset() >= textNode->length();
3434 if (pos.offset() > 0 && !atEnd) {
3435 splitTextNode(textNode, pos.offset());
3436 pos = Position(startNode, 0);
3439 newStartNode = startNode->traverseNextNode();
3440 ASSERT(newStartNode);
3443 else if (pos.offset() > 0) {
3444 newStartNode = startNode->traverseNextNode();
3445 ASSERT(newStartNode);
3448 // If a new start node was determined, find a new top block quote.
3450 startNode = newStartNode;
3451 for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
3452 if (isMailBlockquote(n))
3455 if (!topBlockquote || !topBlockquote->parentNode())
3459 // Build up list of ancestors in between the start node and the top blockquote.
3460 if (startNode != topBlockquote) {
3461 for (NodeImpl *n = startNode->parentNode(); n && n != topBlockquote; n = n->parentNode())
3462 ancestors.prepend(n);
3465 // Insert a clone of the top blockquote after the break.
3466 NodeImpl *clonedBlockquote = topBlockquote->cloneNode(false);
3467 clonedBlockquote->ref();
3468 clonedNodes.append(clonedBlockquote);
3469 insertNodeAfter(clonedBlockquote, m_breakNode);
3471 // Make clones of ancestors in between the start node and the top blockquote.
3472 NodeImpl *parent = clonedBlockquote;
3473 for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
3474 NodeImpl *child = it.current()->cloneNode(false); // shallow clone
3476 clonedNodes.append(child);
3477 appendNode(child, parent);
3481 // Move the start node and the siblings of the start node.
3482 bool startIsBR = false;
3483 if (startNode != topBlockqu