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_elementimpl.h"
36 #include "dom_nodeimpl.h"
37 #include "dom_position.h"
38 #include "dom_stringimpl.h"
39 #include "dom_textimpl.h"
40 #include "dom2_range.h"
41 #include "dom2_rangeimpl.h"
42 #include "html_elementimpl.h"
43 #include "html_imageimpl.h"
44 #include "html_interchange.h"
45 #include "htmlattrs.h"
47 #include "khtml_part.h"
48 #include "khtml_part.h"
49 #include "khtmlview.h"
52 #include "render_object.h"
53 #include "render_style.h"
54 #include "render_text.h"
55 #include "visible_position.h"
56 #include "visible_text.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::DoNotUpdateLayout;
74 using DOM::EditingTextImpl;
75 using DOM::ElementImpl;
76 using DOM::HTMLElementImpl;
77 using DOM::HTMLImageElementImpl;
78 using DOM::NamedAttrMapImpl;
80 using DOM::NodeListImpl;
85 using DOM::TreeWalkerImpl;
88 #include "KWQAssertions.h"
89 #include "KWQLogging.h"
90 #include "KWQKHTMLPart.h"
92 #define ASSERT(assertion) ((void)0)
93 #define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
94 #define ASSERT_NOT_REACHED() ((void)0)
95 #define LOG(channel, formatAndArgs...) ((void)0)
96 #define ERROR(formatAndArgs...) ((void)0)
97 #define ASSERT(assertion) assert(assertion)
99 #define debugPosition(a,b) ((void)0)
100 #define debugNode(a,b) ((void)0)
106 static inline bool isNBSP(const QChar &c)
108 return c.unicode() == 0xa0;
111 // FIXME: Can't really determine this without taking white-space mode into account.
112 static inline bool nextCharacterIsCollapsibleWhitespace(const Position &pos)
116 if (!pos.node()->isTextNode())
118 return isCollapsibleWhitespace(static_cast<TextImpl *>(pos.node())->data()[pos.offset()]);
121 static const int spacesPerTab = 4;
123 static bool isTableStructureNode(const NodeImpl *node)
125 RenderObject *r = node->renderer();
126 return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol()));
129 static bool isListStructureNode(const NodeImpl *node)
131 // FIXME: Irritating that we can get away with just going at the render tree for isTableStructureNode,
132 // but here we also have to peek at the type of DOM node?
133 RenderObject *r = node->renderer();
134 NodeImpl::Id nodeID = node->id();
135 return (r && r->isListItem())
136 || (nodeID == ID_OL || nodeID == ID_UL || nodeID == ID_DD || nodeID == ID_DT || nodeID == ID_DIR || nodeID == ID_MENU);
139 static DOMString &nonBreakingSpaceString()
141 static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
142 return nonBreakingSpaceString;
145 static DOMString &styleSpanClassString()
147 static DOMString styleSpanClassString = AppleStyleSpanClass;
148 return styleSpanClassString;
151 static bool isEmptyStyleSpan(const NodeImpl *node)
153 if (!node || !node->isHTMLElement() || node->id() != ID_SPAN)
156 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
157 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
158 return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
161 static bool isStyleSpan(const NodeImpl *node)
163 if (!node || !node->isHTMLElement())
166 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
167 return elem->id() == ID_SPAN && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
170 static bool isEmptyFontTag(const NodeImpl *node)
172 if (!node || node->id() != ID_FONT)
175 const ElementImpl *elem = static_cast<const ElementImpl *>(node);
176 NamedAttrMapImpl *map = elem->attributes(true); // true for read-only
177 return (!map || map->length() == 1) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
180 static DOMString &blockPlaceholderClassString()
182 static DOMString blockPlaceholderClassString = "khtml-block-placeholder";
183 return blockPlaceholderClassString;
186 static DOMString &matchNearestBlockquoteColorString()
188 static DOMString matchNearestBlockquoteColorString = "match";
189 return matchNearestBlockquoteColorString;
192 static void derefNodesInList(QPtrList<NodeImpl> &list)
194 for (QPtrListIterator<NodeImpl> it(list); it.current(); ++it)
195 it.current()->deref();
198 static int maxRangeOffset(NodeImpl *n)
200 if (DOM::offsetInCharacters(n->nodeType()))
201 return n->maxOffset();
203 if (n->isElementNode())
204 return n->childNodeCount();
209 static int maxDeepOffset(NodeImpl *n)
211 if (n->isAtomicNode())
212 return n->caretMaxOffset();
214 if (n->isElementNode())
215 return n->childNodeCount();
220 static void debugPosition(const char *prefix, const Position &pos)
225 LOG(Editing, "%s <null>", prefix);
227 LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
230 static void debugNode(const char *prefix, const NodeImpl *node)
235 LOG(Editing, "%s <null>", prefix);
237 LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node);
240 //------------------------------------------------------------------------------------------
243 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, ELegacyHTMLStyles usesLegacyStyles)
244 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
246 init(style, Position());
249 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position, ELegacyHTMLStyles usesLegacyStyles)
250 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
252 init(style, position);
255 void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
258 CSSMutableStyleDeclarationImpl *mutableStyle = style->makeMutable();
262 QString styleText("");
264 QValueListConstIterator<CSSProperty> end;
265 for (QValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
266 const CSSProperty *property = &*it;
268 // If position is empty or the position passed in already has the
269 // style, just move on.
270 if (position.isNotNull() && currentlyHasStyle(position, property))
273 // If needed, figure out if this change is a legacy HTML style change.
274 if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property))
279 if (property->id() == CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT) {
280 // we have to special-case text decorations
281 CSSProperty alteredProperty = CSSProperty(CSS_PROP_TEXT_DECORATION, property->value(), property->isImportant());
282 styleText += alteredProperty.cssText().string();
284 styleText += property->cssText().string();
288 mutableStyle->deref();
290 // Save the result for later
291 m_cssStyle = styleText.stripWhiteSpace();
294 StyleChange::ELegacyHTMLStyles StyleChange::styleModeForParseMode(bool isQuirksMode)
296 return isQuirksMode ? UseLegacyHTMLStyles : DoNotUseLegacyHTMLStyles;
299 bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty *property)
301 if (!property || !property->value()) {
305 DOMString valueText(property->value()->cssText());
306 switch (property->id()) {
307 case CSS_PROP_FONT_WEIGHT:
308 if (strcasecmp(valueText, "bold") == 0) {
313 case CSS_PROP_FONT_STYLE:
314 if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) {
315 m_applyItalic = true;
319 case CSS_PROP_COLOR: {
320 QColor color(CSSParser::parseColor(valueText));
321 m_applyFontColor = color.name();
324 case CSS_PROP_FONT_FAMILY:
325 m_applyFontFace = valueText;
327 case CSS_PROP_FONT_SIZE:
328 if (property->value()->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
329 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(property->value());
330 float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
332 m_applyFontSize = "1";
333 else if (number <= 10)
334 m_applyFontSize = "2";
335 else if (number <= 13)
336 m_applyFontSize = "3";
337 else if (number <= 16)
338 m_applyFontSize = "4";
339 else if (number <= 18)
340 m_applyFontSize = "5";
341 else if (number <= 24)
342 m_applyFontSize = "6";
344 m_applyFontSize = "7";
345 // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write
346 // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all,
347 // like Eudora). Yes, they write out *both*. We need to write out both as well. Return false.
351 // Can't make sense of the number. Put no font size.
358 bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
360 ASSERT(pos.isNotNull());
361 CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
364 CSSValueImpl *value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
369 bool result = strcasecmp(value->cssText(), property->value()->cssText()) == 0;
374 //------------------------------------------------------------------------------------------
375 // CompositeEditCommand
377 CompositeEditCommand::CompositeEditCommand(DocumentImpl *document)
378 : EditCommand(document)
382 void CompositeEditCommand::doUnapply()
384 if (m_cmds.count() == 0) {
388 for (int i = m_cmds.count() - 1; i >= 0; --i)
389 m_cmds[i]->unapply();
391 setState(NotApplied);
394 void CompositeEditCommand::doReapply()
396 if (m_cmds.count() == 0) {
400 for (QValueList<EditCommandPtr>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
407 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
409 void CompositeEditCommand::applyCommandToComposite(EditCommandPtr &cmd)
411 cmd.setStartingSelection(endingSelection());
412 cmd.setEndingSelection(endingSelection());
418 void CompositeEditCommand::applyStyle(CSSStyleDeclarationImpl *style, EditAction editingAction)
420 EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction));
421 applyCommandToComposite(cmd);
424 void CompositeEditCommand::insertParagraphSeparator()
426 EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
427 applyCommandToComposite(cmd);
430 void CompositeEditCommand::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
432 ASSERT(refChild->id() != ID_BODY);
433 EditCommandPtr cmd(new InsertNodeBeforeCommand(document(), insertChild, refChild));
434 applyCommandToComposite(cmd);
437 void CompositeEditCommand::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
439 ASSERT(refChild->id() != ID_BODY);
440 if (refChild->parentNode()->lastChild() == refChild) {
441 appendNode(insertChild, refChild->parentNode());
444 ASSERT(refChild->nextSibling());
445 insertNodeBefore(insertChild, refChild->nextSibling());
449 void CompositeEditCommand::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
451 if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
452 NodeImpl *child = refChild->firstChild();
453 for (long i = 0; child && i < offset; i++)
454 child = child->nextSibling();
456 insertNodeBefore(insertChild, child);
458 appendNode(insertChild, refChild);
460 else if (refChild->caretMinOffset() >= offset) {
461 insertNodeBefore(insertChild, refChild);
463 else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
464 splitTextNode(static_cast<TextImpl *>(refChild), offset);
465 insertNodeBefore(insertChild, refChild);
468 insertNodeAfter(insertChild, refChild);
472 void CompositeEditCommand::appendNode(NodeImpl *appendChild, NodeImpl *parent)
474 EditCommandPtr cmd(new AppendNodeCommand(document(), appendChild, parent));
475 applyCommandToComposite(cmd);
478 void CompositeEditCommand::removeFullySelectedNode(NodeImpl *node)
480 if (isTableStructureNode(node) || node == node->rootEditableElement()) {
481 // Do not remove an element of table structure; remove its contents.
482 // Likewise for the root editable element.
483 NodeImpl *child = node->firstChild();
485 NodeImpl *remove = child;
486 child = child->nextSibling();
487 removeFullySelectedNode(remove);
495 void CompositeEditCommand::removeChildrenInRange(NodeImpl *node, int from, int to)
497 NodeImpl *nodeToRemove = node->childNode(from);
498 for (int i = from; i < to; i++) {
499 ASSERT(nodeToRemove);
500 NodeImpl *next = nodeToRemove->nextSibling();
501 removeNode(nodeToRemove);
506 void CompositeEditCommand::removeNode(NodeImpl *removeChild)
508 EditCommandPtr cmd(new RemoveNodeCommand(document(), removeChild));
509 applyCommandToComposite(cmd);
512 void CompositeEditCommand::removeNodePreservingChildren(NodeImpl *removeChild)
514 EditCommandPtr cmd(new RemoveNodePreservingChildrenCommand(document(), removeChild));
515 applyCommandToComposite(cmd);
518 void CompositeEditCommand::splitTextNode(TextImpl *text, long offset)
520 EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, offset));
521 applyCommandToComposite(cmd);
524 void CompositeEditCommand::splitElement(ElementImpl *element, NodeImpl *atChild)
526 EditCommandPtr cmd(new SplitElementCommand(document(), element, atChild));
527 applyCommandToComposite(cmd);
530 void CompositeEditCommand::mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second)
532 EditCommandPtr cmd(new MergeIdenticalElementsCommand(document(), first, second));
533 applyCommandToComposite(cmd);
536 void CompositeEditCommand::wrapContentsInDummySpan(DOM::ElementImpl *element)
538 EditCommandPtr cmd(new WrapContentsInDummySpanCommand(document(), element));
539 applyCommandToComposite(cmd);
542 void CompositeEditCommand::splitTextNodeContainingElement(DOM::TextImpl *text, long offset)
544 EditCommandPtr cmd(new SplitTextNodeContainingElementCommand(document(), text, offset));
545 applyCommandToComposite(cmd);
548 void CompositeEditCommand::joinTextNodes(TextImpl *text1, TextImpl *text2)
550 EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2));
551 applyCommandToComposite(cmd);
554 void CompositeEditCommand::inputText(const DOMString &text, bool selectInsertedText)
556 InsertTextCommand *impl = new InsertTextCommand(document());
557 EditCommandPtr cmd(impl);
558 applyCommandToComposite(cmd);
559 impl->input(text, selectInsertedText);
562 void CompositeEditCommand::insertTextIntoNode(TextImpl *node, long offset, const DOMString &text)
564 EditCommandPtr cmd(new InsertIntoTextNode(document(), node, offset, text));
565 applyCommandToComposite(cmd);
568 void CompositeEditCommand::deleteTextFromNode(TextImpl *node, long offset, long count)
570 EditCommandPtr cmd(new DeleteFromTextNodeCommand(document(), node, offset, count));
571 applyCommandToComposite(cmd);
574 void CompositeEditCommand::replaceTextInNode(TextImpl *node, long offset, long count, const DOMString &replacementText)
576 EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count));
577 applyCommandToComposite(deleteCommand);
578 EditCommandPtr insertCommand(new InsertIntoTextNode(document(), node, offset, replacementText));
579 applyCommandToComposite(insertCommand);
582 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete)
584 if (endingSelection().isRange()) {
585 EditCommandPtr cmd(new DeleteSelectionCommand(document(), smartDelete, mergeBlocksAfterDelete));
586 applyCommandToComposite(cmd);
590 void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
592 if (selection.isRange()) {
593 EditCommandPtr cmd(new DeleteSelectionCommand(document(), selection, smartDelete, mergeBlocksAfterDelete));
594 applyCommandToComposite(cmd);
598 void CompositeEditCommand::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
600 EditCommandPtr cmd(new RemoveCSSPropertyCommand(document(), decl, property));
601 applyCommandToComposite(cmd);
604 void CompositeEditCommand::removeNodeAttribute(ElementImpl *element, int attribute)
606 DOMString value = element->getAttribute(attribute);
609 EditCommandPtr cmd(new RemoveNodeAttributeCommand(document(), element, attribute));
610 applyCommandToComposite(cmd);
613 void CompositeEditCommand::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
615 EditCommandPtr cmd(new SetNodeAttributeCommand(document(), element, attribute, value));
616 applyCommandToComposite(cmd);
619 void CompositeEditCommand::rebalanceWhitespace()
621 Selection selection = endingSelection();
622 if (selection.isCaretOrRange()) {
623 EditCommandPtr startCmd(new RebalanceWhitespaceCommand(document(), endingSelection().start()));
624 applyCommandToComposite(startCmd);
625 if (selection.isRange()) {
626 EditCommandPtr endCmd(new RebalanceWhitespaceCommand(document(), endingSelection().end()));
627 applyCommandToComposite(endCmd);
632 void CompositeEditCommand::deleteInsignificantText(TextImpl *textNode, int start, int end)
634 if (!textNode || !textNode->renderer() || start >= end)
637 RenderText *textRenderer = static_cast<RenderText *>(textNode->renderer());
638 InlineTextBox *box = textRenderer->firstTextBox();
640 // whole text node is empty
641 removeNode(textNode);
645 long length = textNode->length();
646 if (start >= length || end > length)
650 InlineTextBox *prevBox = 0;
651 DOMStringImpl *str = 0;
653 // This loop structure works to process all gaps preceding a box,
654 // and also will look at the gap after the last box.
655 while (prevBox || box) {
656 int gapStart = prevBox ? prevBox->m_start + prevBox->m_len : 0;
658 // No more chance for any intersections
661 int gapEnd = box ? box->m_start : length;
662 bool indicesIntersect = start <= gapEnd && end >= gapStart;
663 int gapLen = gapEnd - gapStart;
664 if (indicesIntersect && gapLen > 0) {
665 gapStart = kMax(gapStart, start);
666 gapEnd = kMin(gapEnd, end);
668 str = textNode->string()->substring(start, end - start);
671 // remove text in the gap
672 str->remove(gapStart - start - removed, gapLen);
678 box = box->nextTextBox();
682 // Replace the text between start and end with our pruned version.
684 replaceTextInNode(textNode, start, end - start, str);
687 // Assert that we are not going to delete all of the text in the node.
688 // If we were, that should have been done above with the call to
689 // removeNode and return.
690 ASSERT(start > 0 || (unsigned long)end - start < textNode->length());
691 deleteTextFromNode(textNode, start, end - start);
697 void CompositeEditCommand::deleteInsignificantText(const Position &start, const Position &end)
699 if (start.isNull() || end.isNull())
702 if (RangeImpl::compareBoundaryPoints(start, end) >= 0)
705 NodeImpl *node = start.node();
707 NodeImpl *next = node->traverseNextNode();
709 if (node->isTextNode()) {
710 TextImpl *textNode = static_cast<TextImpl *>(node);
711 bool isStartNode = node == start.node();
712 bool isEndNode = node == end.node();
713 int startOffset = isStartNode ? start.offset() : 0;
714 int endOffset = isEndNode ? end.offset() : textNode->length();
715 deleteInsignificantText(textNode, startOffset, endOffset);
718 if (node == end.node())
724 void CompositeEditCommand::deleteInsignificantTextDownstream(const DOM::Position &pos)
726 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream();
727 deleteInsignificantText(pos, end);
730 NodeImpl *CompositeEditCommand::appendBlockPlaceholder(NodeImpl *node)
735 ASSERT(node->renderer() && node->renderer()->isBlockFlow());
737 NodeImpl *placeholder = createBlockPlaceholderElement(document());
738 appendNode(placeholder, node);
742 NodeImpl *CompositeEditCommand::insertBlockPlaceholder(const Position &pos)
747 ASSERT(pos.node()->renderer() && pos.node()->renderer()->isBlockFlow());
749 NodeImpl *placeholder = createBlockPlaceholderElement(document());
750 insertNodeAt(placeholder, pos.node(), pos.offset());
754 NodeImpl *CompositeEditCommand::addBlockPlaceholderIfNeeded(NodeImpl *node)
759 document()->updateLayout();
761 RenderObject *renderer = node->renderer();
762 if (!renderer || !renderer->isBlockFlow())
765 // append the placeholder to make sure it follows
766 // any unrendered blocks
767 if (renderer->height() == 0) {
768 return appendBlockPlaceholder(node);
774 bool CompositeEditCommand::removeBlockPlaceholder(NodeImpl *node)
776 NodeImpl *placeholder = findBlockPlaceholder(node);
778 removeNode(placeholder);
784 NodeImpl *CompositeEditCommand::findBlockPlaceholder(NodeImpl *node)
789 document()->updateLayout();
791 RenderObject *renderer = node->renderer();
792 if (!renderer || !renderer->isBlockFlow())
795 for (NodeImpl *checkMe = node; checkMe; checkMe = checkMe->traverseNextNode(node)) {
796 if (checkMe->isElementNode()) {
797 ElementImpl *element = static_cast<ElementImpl *>(checkMe);
798 if (element->enclosingBlockFlowElement() == node &&
799 element->getAttribute(ATTR_CLASS) == blockPlaceholderClassString()) {
808 void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position &pos)
813 document()->updateLayout();
815 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
816 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
817 VisiblePosition visibleParagraphEnd(endOfParagraph(visiblePos, IncludeLineBreak));
818 Position paragraphStart = visibleParagraphStart.deepEquivalent().upstream();
819 Position paragraphEnd = visibleParagraphEnd.deepEquivalent().upstream();
821 // Perform some checks to see if we need to perform work in this function.
822 if (paragraphStart.node()->isBlockFlow()) {
823 if (paragraphEnd.node()->isBlockFlow()) {
824 if (!paragraphEnd.node()->isAncestor(paragraphStart.node())) {
825 // If the paragraph end is a descendant of paragraph start, then we need to run
826 // the rest of this function. If not, we can bail here.
830 else if (paragraphEnd.node()->enclosingBlockFlowElement() != paragraphStart.node()) {
831 // The paragraph end is in another block that is an ancestor of the paragraph start.
832 // We can bail as we have a full block to work with.
833 ASSERT(paragraphStart.node()->isAncestor(paragraphEnd.node()->enclosingBlockFlowElement()));
836 else if (isEndOfDocument(visibleParagraphEnd)) {
837 // At the end of the document. We can bail here as well.
842 // Create the block to insert. Most times, this will be a shallow clone of the block containing
843 // the start of the selection (the start block), except for two cases:
844 // 1) When the start block is a body element.
845 // 2) When the start block is a mail blockquote and we are not in a position to insert
846 // the new block as a peer of the start block. This prevents creating an unwanted
847 // additional level of quoting.
848 NodeImpl *startBlock = paragraphStart.node()->enclosingBlockFlowElement();
849 NodeImpl *newBlock = 0;
850 if (startBlock->id() == ID_BODY || (isMailBlockquote(startBlock) && paragraphStart.node() != startBlock))
851 newBlock = createDefaultParagraphElement(document());
853 newBlock = startBlock->cloneNode(false);
855 NodeImpl *moveNode = paragraphStart.node();
856 if (paragraphStart.offset() >= paragraphStart.node()->caretMaxOffset())
857 moveNode = moveNode->traverseNextNode();
858 NodeImpl *endNode = paragraphEnd.node();
860 if (paragraphStart.node()->id() == ID_BODY) {
861 insertNodeAt(newBlock, paragraphStart.node(), 0);
863 else if (paragraphStart.node()->id() == ID_BR) {
864 insertNodeAfter(newBlock, paragraphStart.node());
867 insertNodeBefore(newBlock, paragraphStart.upstream().node());
870 while (moveNode && !moveNode->isBlockFlow()) {
871 NodeImpl *next = moveNode->traverseNextSibling();
872 removeNode(moveNode);
873 appendNode(moveNode, newBlock);
874 if (moveNode == endNode)
880 static bool isSpecialElement(NodeImpl *n)
882 if (!n->isHTMLElement())
885 if (n->id() == ID_A && n->isLink())
888 if (n->id() == ID_UL || n->id() == ID_OL || n->id() == ID_DL)
891 RenderObject *renderer = n->renderer();
893 if (renderer && (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE))
896 if (renderer && renderer->style()->isFloating())
899 if (renderer && renderer->style()->position() != STATIC)
905 // This version of the function is meant to be called on positions in a document fragment,
906 // so it does not check for a root editable element, it is assumed these nodes will be put
907 // somewhere editable in the future
908 static bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos)
910 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
912 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
913 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
915 if (isSpecialElement(n))
922 static bool isFirstVisiblePositionInSpecialElement(const Position& pos)
924 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
926 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
927 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
929 if (n->rootEditableElement() == NULL)
931 if (isSpecialElement(n))
938 static Position positionBeforeNode(NodeImpl *node)
940 return Position(node->parentNode(), node->nodeIndex());
943 static Position positionBeforeContainingSpecialElement(const Position& pos)
945 ASSERT(isFirstVisiblePositionInSpecialElement(pos));
947 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
949 NodeImpl *outermostSpecialElement = NULL;
951 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
952 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
954 if (n->rootEditableElement() == NULL)
956 if (isSpecialElement(n))
957 outermostSpecialElement = n;
960 ASSERT(outermostSpecialElement);
962 Position result = positionBeforeNode(outermostSpecialElement);
963 if (result.isNull() || !result.node()->rootEditableElement())
969 static bool isLastVisiblePositionInSpecialElement(const Position& pos)
971 // make sure to get a range-compliant version of the position
972 Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
974 VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
976 for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
977 if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
979 if (n->rootEditableElement() == NULL)
981 if (isSpecialElement(n))
988 static Position positionAfterNode(NodeImpl *node)
990 return Position(node->parentNode(), node->nodeIndex() + 1);
993 static Position positionAfterContainingSpecialElement(const Position& pos)
995 ASSERT(isLastVisiblePositionInSpecialElement(pos));
997 // make sure to get a range-compliant version of the position
998 Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
1000 VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
1002 NodeImpl *outermostSpecialElement = NULL;
1004 for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
1005 if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
1007 if (n->rootEditableElement() == NULL)
1009 if (isSpecialElement(n))
1010 outermostSpecialElement = n;
1013 ASSERT(outermostSpecialElement);
1015 Position result = positionAfterNode(outermostSpecialElement);
1016 if (result.isNull() || !result.node()->rootEditableElement())
1022 static Position positionOutsideContainingSpecialElement(const Position &pos)
1024 if (isFirstVisiblePositionInSpecialElement(pos)) {
1025 return positionBeforeContainingSpecialElement(pos);
1026 } else if (isLastVisiblePositionInSpecialElement(pos)) {
1027 return positionAfterContainingSpecialElement(pos);
1033 static Position positionBeforePossibleContainingSpecialElement(const Position &pos)
1035 if (isFirstVisiblePositionInSpecialElement(pos)) {
1036 return positionBeforeContainingSpecialElement(pos);
1042 static Position positionAfterPossibleContainingSpecialElement(const Position &pos)
1044 if (isLastVisiblePositionInSpecialElement(pos)) {
1045 return positionAfterContainingSpecialElement(pos);
1051 //==========================================================================================
1052 // Concrete commands
1053 //------------------------------------------------------------------------------------------
1054 // AppendNodeCommand
1056 AppendNodeCommand::AppendNodeCommand(DocumentImpl *document, NodeImpl *appendChild, NodeImpl *parentNode)
1057 : EditCommand(document), m_appendChild(appendChild), m_parentNode(parentNode)
1059 ASSERT(m_appendChild);
1060 m_appendChild->ref();
1062 ASSERT(m_parentNode);
1063 m_parentNode->ref();
1066 AppendNodeCommand::~AppendNodeCommand()
1068 ASSERT(m_appendChild);
1069 m_appendChild->deref();
1071 ASSERT(m_parentNode);
1072 m_parentNode->deref();
1075 void AppendNodeCommand::doApply()
1077 ASSERT(m_appendChild);
1078 ASSERT(m_parentNode);
1080 int exceptionCode = 0;
1081 m_parentNode->appendChild(m_appendChild, exceptionCode);
1082 ASSERT(exceptionCode == 0);
1085 void AppendNodeCommand::doUnapply()
1087 ASSERT(m_appendChild);
1088 ASSERT(m_parentNode);
1089 ASSERT(state() == Applied);
1091 int exceptionCode = 0;
1092 m_parentNode->removeChild(m_appendChild, exceptionCode);
1093 ASSERT(exceptionCode == 0);
1096 //------------------------------------------------------------------------------------------
1097 // ApplyStyleCommand
1099 ApplyStyleCommand::ApplyStyleCommand(DocumentImpl *document, CSSStyleDeclarationImpl *style, EditAction editingAction, EPropertyLevel propertyLevel)
1100 : CompositeEditCommand(document), m_style(style->makeMutable()), m_editingAction(editingAction), m_propertyLevel(propertyLevel)
1106 ApplyStyleCommand::~ApplyStyleCommand()
1112 void ApplyStyleCommand::doApply()
1114 switch (m_propertyLevel) {
1115 case PropertyDefault: {
1116 // apply the block-centric properties of the style
1117 CSSMutableStyleDeclarationImpl *blockStyle = m_style->copyBlockProperties();
1119 applyBlockStyle(blockStyle);
1120 // apply any remaining styles to the inline elements
1121 // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
1122 if (blockStyle->length() < m_style->length()) {
1123 CSSMutableStyleDeclarationImpl *inlineStyle = m_style->copy();
1125 applyRelativeFontStyleChange(inlineStyle);
1126 blockStyle->diff(inlineStyle);
1127 applyInlineStyle(inlineStyle);
1128 inlineStyle->deref();
1130 blockStyle->deref();
1133 case ForceBlockProperties:
1134 // Force all properties to be applied as block styles.
1135 applyBlockStyle(m_style);
1139 setEndingSelectionNeedsLayout();
1142 EditAction ApplyStyleCommand::editingAction() const
1144 return m_editingAction;
1147 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclarationImpl *style)
1149 // update document layout once before removing styles
1150 // so that we avoid the expense of updating before each and every call
1151 // to check a computed style
1152 document()->updateLayout();
1154 // get positions we want to use for applying style
1155 Position start(endingSelection().start());
1156 Position end(endingSelection().end());
1158 // remove current values, if any, of the specified styles from the blocks
1159 // NOTE: tracks the previous block to avoid repeated processing
1160 // Also, gather up all the nodes we want to process in a QPtrList before
1161 // doing anything. This averts any bugs iterating over these nodes
1162 // once you start removing and applying style.
1163 NodeImpl *beyondEnd = end.node()->traverseNextNode();
1164 QPtrList<NodeImpl> nodes;
1165 for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode())
1168 NodeImpl *prevBlock = 0;
1169 for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
1170 NodeImpl *block = it.current()->enclosingBlockFlowElement();
1171 if (block != prevBlock && block->isHTMLElement()) {
1172 removeCSSStyle(style, static_cast<HTMLElementImpl *>(block));
1177 // apply specified styles to the block flow elements in the selected range
1179 for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
1180 NodeImpl *node = it.current();
1181 if (node->renderer()) {
1182 NodeImpl *block = node->enclosingBlockFlowElement();
1183 if (block != prevBlock) {
1184 addBlockStyleIfNeeded(style, node);
1191 #define NoFontDelta (0.0f)
1192 #define MinimumFontSize (0.1f)
1194 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclarationImpl *style)
1196 if (style->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
1197 // Explicit font size overrides any delta.
1198 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1202 // Get the adjustment amount out of the style.
1203 CSSValueImpl *value = style->getPropertyCSSValue(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1207 float adjustment = NoFontDelta;
1208 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
1209 CSSPrimitiveValueImpl *primitiveValue = static_cast<CSSPrimitiveValueImpl *>(value);
1210 if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
1211 // Only PX handled now. If we handle more types in the future, perhaps
1212 // a switch statement here would be more appropriate.
1213 adjustment = primitiveValue->getFloatValue(CSSPrimitiveValue::CSS_PX);
1216 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1218 if (adjustment == NoFontDelta)
1221 // Adjust to the positions we want to use for applying style.
1222 Selection selection = endingSelection();
1223 Position start(selection.start().downstream());
1224 Position end(selection.end().upstream());
1225 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
1226 Position swap = start;
1231 // Join up any adjacent text nodes.
1232 if (start.node()->isTextNode()) {
1233 joinChildTextNodes(start.node()->parentNode(), start, end);
1234 selection = endingSelection();
1235 start = selection.start();
1236 end = selection.end();
1238 if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
1239 joinChildTextNodes(end.node()->parentNode(), start, end);
1240 selection = endingSelection();
1241 start = selection.start();
1242 end = selection.end();
1245 // Split the start text nodes if needed to apply style.
1246 bool splitStart = splitTextAtStartIfNeeded(start, end);
1248 start = endingSelection().start();
1249 end = endingSelection().end();
1251 bool splitEnd = splitTextAtEndIfNeeded(start, end);
1253 start = endingSelection().start();
1254 end = endingSelection().end();
1257 NodeImpl *beyondEnd = end.node()->traverseNextNode(); // Calculate loop end point.
1258 start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
1259 NodeImpl *startNode = start.node();
1260 if (startNode->isTextNode() && start.offset() >= startNode->caretMaxOffset()) // Move out of text node if range does not include its characters.
1261 startNode = startNode->traverseNextNode();
1263 // Store away font size before making any changes to the document.
1264 // This ensures that changes to one node won't effect another.
1265 QMap<const NodeImpl *,float> startingFontSizes;
1266 for (const NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode())
1267 startingFontSizes.insert(node, computedFontSize(node));
1269 // These spans were added by us. If empty after font size changes, they can be removed.
1270 QPtrList<NodeImpl> emptySpans;
1272 NodeImpl *lastStyledNode = 0;
1273 for (NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
1274 HTMLElementImpl *elem = 0;
1275 if (node->isHTMLElement()) {
1276 // Only work on fully selected nodes.
1277 if (!nodeFullySelected(node, start, end))
1279 elem = static_cast<HTMLElementImpl *>(node);
1281 else if (node->isTextNode() && node->parentNode() != lastStyledNode) {
1282 // Last styled node was not parent node of this text node, but we wish to style this
1283 // text node. To make this possible, add a style span to surround this text node.
1284 elem = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
1285 insertNodeBefore(elem, node);
1286 surroundNodeRangeWithElement(node, node, elem);
1289 // Only handle HTML elements and text nodes.
1292 lastStyledNode = node;
1294 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->getInlineStyleDecl();
1295 float currentFontSize = computedFontSize(node);
1296 float desiredFontSize = kMax(MinimumFontSize, startingFontSizes[node] + adjustment);
1297 if (inlineStyleDecl->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
1298 inlineStyleDecl->removeProperty(CSS_PROP_FONT_SIZE, true);
1299 currentFontSize = computedFontSize(node);
1301 if (currentFontSize != desiredFontSize) {
1302 QString desiredFontSizeString = QString::number(desiredFontSize);
1303 desiredFontSizeString += "px";
1304 inlineStyleDecl->setProperty(CSS_PROP_FONT_SIZE, desiredFontSizeString, false, false);
1305 setNodeAttribute(elem, ATTR_STYLE, inlineStyleDecl->cssText());
1307 if (inlineStyleDecl->length() == 0) {
1308 removeNodeAttribute(elem, ATTR_STYLE);
1309 if (isEmptyStyleSpan(elem))
1310 emptySpans.append(elem);
1314 for (QPtrListIterator<NodeImpl> it(emptySpans); it.current(); ++it)
1315 removeNodePreservingChildren(it.current());
1319 #undef MinimumFontSize
1321 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style)
1323 // adjust to the positions we want to use for applying style
1324 Position start(endingSelection().start().downstream().equivalentRangeCompliantPosition());
1325 Position end(endingSelection().end().upstream());
1327 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
1328 Position swap = start;
1333 // update document layout once before removing styles
1334 // so that we avoid the expense of updating before each and every call
1335 // to check a computed style
1336 document()->updateLayout();
1338 // split the start node and containing element if the selection starts inside of it
1339 bool splitStart = splitTextElementAtStartIfNeeded(start, end);
1341 start = endingSelection().start();
1342 end = endingSelection().end();
1345 // split the end node and containing element if the selection ends inside of it
1346 bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
1347 start = endingSelection().start();
1348 end = endingSelection().end();
1350 // Remove style from the selection.
1351 // Use the upstream position of the start for removing style.
1352 // This will ensure we remove all traces of the relevant styles from the selection
1353 // and prevent us from adding redundant ones, as described in:
1354 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
1355 removeInlineStyle(style, start.upstream(), end);
1356 start = endingSelection().start();
1357 end = endingSelection().end();
1360 bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
1362 start = endingSelection().start();
1363 end = endingSelection().end();
1368 mergeEndWithNextIfIdentical(start, end);
1369 start = endingSelection().start();
1370 end = endingSelection().end();
1373 // update document layout once before running the rest of the function
1374 // so that we avoid the expense of updating before each and every call
1375 // to check a computed style
1376 document()->updateLayout();
1378 if (start.node() == end.node()) {
1379 // simple case...start and end are the same node
1380 addInlineStyleIfNeeded(style, start.node(), end.node());
1383 NodeImpl *node = start.node();
1384 if (start.offset() >= start.node()->caretMaxOffset())
1385 node = node->traverseNextNode();
1387 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
1388 NodeImpl *runStart = node;
1390 NodeImpl *next = node->traverseNextNode();
1391 // Break if node is the end node, or if the next node does not fit in with
1392 // the current group.
1393 if (node == end.node() ||
1394 runStart->parentNode() != next->parentNode() ||
1395 (next->isHTMLElement() && next->id() != ID_BR) ||
1396 (next->renderer() && !next->renderer()->isInline()))
1400 // Now apply style to the run we found.
1401 addInlineStyleIfNeeded(style, runStart, node);
1403 if (node == end.node())
1405 node = node->traverseNextNode();
1409 if (splitStart || splitEnd) {
1410 cleanUpEmptyStyleSpans(start, end);
1414 //------------------------------------------------------------------------------------------
1415 // ApplyStyleCommand: style-removal helpers
1417 bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1419 QValueListConstIterator<CSSProperty> end;
1420 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1421 switch ((*it).id()) {
1422 case CSS_PROP_FONT_WEIGHT:
1423 if (elem->id() == ID_B)
1426 case CSS_PROP_FONT_STYLE:
1427 if (elem->id() == ID_I)
1435 void ApplyStyleCommand::removeHTMLStyleNode(HTMLElementImpl *elem)
1437 // This node can be removed.
1438 // EDIT FIXME: This does not handle the case where the node
1439 // has attributes. But how often do people add attributes to <B> tags?
1440 // Not so often I think.
1442 removeNodePreservingChildren(elem);
1445 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1450 if (elem->id() != ID_FONT)
1453 int exceptionCode = 0;
1454 QValueListConstIterator<CSSProperty> end;
1455 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1456 switch ((*it).id()) {
1457 case CSS_PROP_COLOR:
1458 elem->removeAttribute(ATTR_COLOR, exceptionCode);
1459 ASSERT(exceptionCode == 0);
1461 case CSS_PROP_FONT_FAMILY:
1462 elem->removeAttribute(ATTR_FACE, exceptionCode);
1463 ASSERT(exceptionCode == 0);
1465 case CSS_PROP_FONT_SIZE:
1466 elem->removeAttribute(ATTR_SIZE, exceptionCode);
1467 ASSERT(exceptionCode == 0);
1472 if (isEmptyFontTag(elem))
1473 removeNodePreservingChildren(elem);
1476 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1481 CSSMutableStyleDeclarationImpl *decl = elem->inlineStyleDecl();
1485 QValueListConstIterator<CSSProperty> end;
1486 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1487 int propertyID = (*it).id();
1488 CSSValueImpl *value = decl->getPropertyCSSValue(propertyID);
1491 removeCSSProperty(decl, propertyID);
1496 if (isEmptyStyleSpan(elem))
1497 removeNodePreservingChildren(elem);
1500 void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
1502 ASSERT(start.isNotNull());
1503 ASSERT(end.isNotNull());
1504 ASSERT(start.node()->inDocument());
1505 ASSERT(end.node()->inDocument());
1506 ASSERT(RangeImpl::compareBoundaryPoints(start, end) <= 0);
1510 static bool hasTextDecorationProperty(NodeImpl *node)
1512 if (!node->isElementNode())
1515 ElementImpl *element = static_cast<ElementImpl *>(node);
1516 CSSComputedStyleDeclarationImpl style(element);
1518 CSSValueImpl *value = style.getPropertyCSSValue(CSS_PROP_TEXT_DECORATION, DoNotUpdateLayout);
1522 DOMString valueText(value->cssText());
1524 if (strcasecmp(valueText,"none") != 0)
1531 static NodeImpl* highestAncestorWithTextDecoration(NodeImpl *node)
1533 NodeImpl *result = NULL;
1535 for (NodeImpl *n = node; n; n = n->parentNode()) {
1536 if (hasTextDecorationProperty(n))
1543 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractTextDecorationStyle(NodeImpl *node)
1546 ASSERT(node->isElementNode());
1548 // non-html elements not handled yet
1549 if (!node->isHTMLElement())
1552 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
1553 CSSMutableStyleDeclarationImpl *style = element->inlineStyleDecl();
1558 int properties[1] = { CSS_PROP_TEXT_DECORATION };
1559 CSSMutableStyleDeclarationImpl *textDecorationStyle = style->copyPropertiesInSet(properties, 1);
1561 CSSValueImpl *property = style->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
1562 if (property && strcasecmp(property->cssText(), "none") != 0) {
1563 removeCSSProperty(style, CSS_PROP_TEXT_DECORATION);
1568 return textDecorationStyle;
1571 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractAndNegateTextDecorationStyle(NodeImpl *node)
1574 ASSERT(node->isElementNode());
1576 // non-html elements not handled yet
1577 if (!node->isHTMLElement())
1580 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
1581 CSSComputedStyleDeclarationImpl *computedStyle = new CSSComputedStyleDeclarationImpl(element);
1582 ASSERT(computedStyle);
1584 computedStyle->ref();
1586 int properties[1] = { CSS_PROP_TEXT_DECORATION };
1587 CSSMutableStyleDeclarationImpl *textDecorationStyle = computedStyle->copyPropertiesInSet(properties, 1);
1590 CSSValueImpl *property = computedStyle->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
1591 if (property && strcasecmp(property->cssText(), "none") != 0) {
1593 CSSMutableStyleDeclarationImpl *newStyle = textDecorationStyle->copy();
1596 newStyle->setProperty(CSS_PROP_TEXT_DECORATION, "none");
1597 applyTextDecorationStyle(node, newStyle);
1603 computedStyle->deref();
1605 return textDecorationStyle;
1608 void ApplyStyleCommand::applyTextDecorationStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style)
1612 if (!style || !style->cssText().length())
1615 if (node->isTextNode()) {
1616 HTMLElementImpl *styleSpan = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
1617 insertNodeBefore(styleSpan, node);
1618 surroundNodeRangeWithElement(node, node, styleSpan);
1622 if (!node->isElementNode())
1625 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
1627 StyleChange styleChange(style, Position(element, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
1628 if (styleChange.cssStyle().length() > 0) {
1629 DOMString cssText = styleChange.cssStyle();
1630 CSSMutableStyleDeclarationImpl *decl = element->inlineStyleDecl();
1632 cssText += decl->cssText();
1633 setNodeAttribute(element, ATTR_STYLE, cssText);
1637 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(NodeImpl *node, const Position &start, const Position &end, bool force)
1639 NodeImpl *highestAncestor = highestAncestorWithTextDecoration(node);
1641 if (highestAncestor) {
1642 NodeImpl *nextCurrent;
1643 NodeImpl *nextChild;
1644 for (NodeImpl *current = highestAncestor; current != node; current = nextCurrent) {
1649 CSSMutableStyleDeclarationImpl *decoration = force ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
1653 for (NodeImpl *child = current->firstChild(); child; child = nextChild) {
1654 nextChild = child->nextSibling();
1656 if (node == child) {
1657 nextCurrent = child;
1658 } else if (node->isAncestor(child)) {
1659 applyTextDecorationStyle(child, decoration);
1660 nextCurrent = child;
1662 applyTextDecorationStyle(child, decoration);
1667 decoration->deref();
1672 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end)
1674 // We need to work in two passes. First we push down any inline
1675 // styles that set text decoration. Then we look for any remaining
1676 // styles (caused by stylesheets) and explicitly negate text
1677 // decoration while pushing down.
1679 pushDownTextDecorationStyleAroundNode(start.node(), start, end, false);
1680 document()->updateLayout();
1681 pushDownTextDecorationStyleAroundNode(start.node(), start, end, true);
1683 pushDownTextDecorationStyleAroundNode(end.node(), start, end, false);
1684 document()->updateLayout();
1685 pushDownTextDecorationStyleAroundNode(end.node(), start, end, true);
1688 void ApplyStyleCommand::removeInlineStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
1690 ASSERT(start.isNotNull());
1691 ASSERT(end.isNotNull());
1692 ASSERT(start.node()->inDocument());
1693 ASSERT(end.node()->inDocument());
1694 ASSERT(RangeImpl::compareBoundaryPoints(start, end) < 0);
1696 CSSValueImpl *textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT);
1698 if (textDecorationSpecialProperty) {
1699 pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream());
1700 style = style->copy();
1701 style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT));
1704 // The s and e variables store the positions used to set the ending selection after style removal
1705 // takes place. This will help callers to recognize when either the start node or the end node
1706 // are removed from the document during the work of this function.
1710 NodeImpl *node = start.node();
1712 NodeImpl *next = node->traverseNextNode();
1713 if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
1714 HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
1715 NodeImpl *prev = elem->traversePreviousNodePostOrder();
1716 NodeImpl *next = elem->traverseNextNode();
1717 if (isHTMLStyleNode(style, elem)) {
1718 removeHTMLStyleNode(elem);
1721 removeHTMLFontStyle(style, elem);
1722 removeCSSStyle(style, elem);
1724 if (!elem->inDocument()) {
1725 if (s.node() == elem) {
1726 // Since elem must have been fully selected, and it is at the start
1727 // of the selection, it is clear we can set the new s offset to 0.
1728 ASSERT(s.offset() <= s.node()->caretMinOffset());
1729 s = Position(next, 0);
1731 if (e.node() == elem) {
1732 // Since elem must have been fully selected, and it is at the end
1733 // of the selection, it is clear we can set the new e offset to
1734 // the max range offset of prev.
1735 ASSERT(e.offset() >= maxRangeOffset(e.node()));
1736 e = Position(prev, maxRangeOffset(prev));
1740 if (node == end.node())
1746 if (textDecorationSpecialProperty) {
1750 ASSERT(s.node()->inDocument());
1751 ASSERT(e.node()->inDocument());
1752 setEndingSelection(Selection(s, VP_DEFAULT_AFFINITY, e, VP_DEFAULT_AFFINITY));
1755 bool ApplyStyleCommand::nodeFullySelected(NodeImpl *node, const Position &start, const Position &end) const
1758 ASSERT(node->isElementNode());
1760 Position pos = Position(node, node->childNodeCount()).upstream();
1761 return RangeImpl::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 &&
1762 RangeImpl::compareBoundaryPoints(pos, end) <= 0;
1765 bool ApplyStyleCommand::nodeFullyUnselected(NodeImpl *node, const Position &start, const Position &end) const
1768 ASSERT(node->isElementNode());
1770 Position pos = Position(node, node->childNodeCount()).upstream();
1771 bool isFullyBeforeStart = RangeImpl::compareBoundaryPoints(pos, start) < 0;
1772 bool isFullyAfterEnd = RangeImpl::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0;
1774 return isFullyBeforeStart || isFullyAfterEnd;
1778 //------------------------------------------------------------------------------------------
1779 // ApplyStyleCommand: style-application helpers
1781 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
1783 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
1784 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
1785 TextImpl *text = static_cast<TextImpl *>(start.node());
1786 splitTextNode(text, start.offset());
1787 setEndingSelection(Selection(Position(start.node(), 0), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
1793 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
1795 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
1796 TextImpl *text = static_cast<TextImpl *>(end.node());
1797 splitTextNode(text, end.offset());
1799 NodeImpl *prevNode = text->previousSibling();
1801 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
1803 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode, prevNode->caretMaxOffset()), SEL_DEFAULT_AFFINITY));
1809 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
1811 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
1812 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
1813 TextImpl *text = static_cast<TextImpl *>(start.node());
1814 splitTextNodeContainingElement(text, start.offset());
1816 setEndingSelection(Selection(Position(start.node()->parentNode(), start.node()->nodeIndex()), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
1822 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
1824 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
1825 TextImpl *text = static_cast<TextImpl *>(end.node());
1826 splitTextNodeContainingElement(text, end.offset());
1828 NodeImpl *prevNode = text->parent()->previousSibling()->lastChild();
1830 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
1832 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode->parent(), prevNode->nodeIndex() + 1), SEL_DEFAULT_AFFINITY));
1838 static bool areIdenticalElements(NodeImpl *first, NodeImpl *second)
1840 // check that tag name and all attribute names and values are identical
1842 if (!first->isElementNode())
1845 if (!second->isElementNode())
1848 ElementImpl *firstElement = static_cast<ElementImpl *>(first);
1849 ElementImpl *secondElement = static_cast<ElementImpl *>(second);
1851 if (firstElement->id() != secondElement->id())
1854 NamedAttrMapImpl *firstMap = firstElement->attributes();
1855 NamedAttrMapImpl *secondMap = secondElement->attributes();
1857 unsigned firstLength = firstMap->length();
1859 if (firstLength != secondMap->length())
1862 for (unsigned i = 0; i < firstLength; i++) {
1863 DOM::AttributeImpl *attribute = firstMap->attributeItem(i);
1864 DOM::AttributeImpl *secondAttribute = secondMap->getAttributeItem(attribute->id());
1866 if (!secondAttribute || attribute->value() != secondAttribute->value())
1873 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
1875 NodeImpl *startNode = start.node();
1876 long startOffset = start.offset();
1878 if (start.node()->isAtomicNode()) {
1879 if (start.offset() != 0)
1882 if (start.node()->previousSibling())
1885 startNode = start.node()->parent();
1889 if (!startNode->isElementNode())
1892 if (startOffset != 0)
1895 NodeImpl *previousSibling = startNode->previousSibling();
1897 if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
1898 ElementImpl *previousElement = static_cast<ElementImpl *>(previousSibling);
1899 ElementImpl *element = static_cast<ElementImpl *>(startNode);
1900 NodeImpl *startChild = element->firstChild();
1902 mergeIdenticalElements(previousElement, element);
1904 long startOffsetAdjustment = startChild->nodeIndex();
1905 long endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
1907 setEndingSelection(Selection(Position(startNode, startOffsetAdjustment), SEL_DEFAULT_AFFINITY,
1908 Position(end.node(), end.offset() + endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
1916 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
1918 NodeImpl *endNode = end.node();
1919 int endOffset = end.offset();
1921 if (endNode->isAtomicNode()) {
1922 if (endOffset < endNode->caretMaxOffset())
1925 unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
1926 if (end.node()->nextSibling())
1929 endNode = end.node()->parent();
1930 endOffset = parentLastOffset;
1933 if (!endNode->isElementNode() || endNode->id() == ID_BR)
1936 NodeImpl *nextSibling = endNode->nextSibling();
1938 if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
1939 ElementImpl *nextElement = static_cast<ElementImpl *>(nextSibling);
1940 ElementImpl *element = static_cast<ElementImpl *>(endNode);
1941 NodeImpl *nextChild = nextElement->firstChild();
1943 mergeIdenticalElements(element, nextElement);
1945 NodeImpl *startNode = start.node() == endNode ? nextElement : start.node();
1948 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
1950 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY,
1951 Position(nextElement, endOffset), SEL_DEFAULT_AFFINITY));
1958 void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end)
1961 for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) {
1964 if (node && isEmptyStyleSpan(node->previousSibling())) {
1965 removeNodePreservingChildren(node->previousSibling());
1968 if (start.node() == end.node()) {
1969 if (start.node()->isTextNode()) {
1970 for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
1971 if (isEmptyStyleSpan(cur)) {
1972 removeNodePreservingChildren(cur);
1979 if (start.node()->isTextNode()) {
1980 for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) {
1981 if (isEmptyStyleSpan(cur)) {
1982 removeNodePreservingChildren(cur);
1988 if (end.node()->isTextNode()) {
1989 for (NodeImpl *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
1990 if (isEmptyStyleSpan(cur)) {
1991 removeNodePreservingChildren(cur);
1998 for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) {
2000 if (node && isEmptyStyleSpan(node->nextSibling())) {
2001 removeNodePreservingChildren(node->nextSibling());
2005 void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
2011 NodeImpl *node = startNode;
2013 NodeImpl *next = node->traverseNextNode();
2014 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
2016 appendNode(node, element);
2018 if (node == endNode)
2024 void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *node)
2026 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
2031 HTMLElementImpl *block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
2035 StyleChange styleChange(style, Position(block, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
2036 if (styleChange.cssStyle().length() > 0) {
2037 moveParagraphContentsToNewBlockIfNecessary(Position(node, 0));
2038 block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
2039 DOMString cssText = styleChange.cssStyle();
2040 CSSMutableStyleDeclarationImpl *decl = block->inlineStyleDecl();
2042 cssText += decl->cssText();
2043 setNodeAttribute(block, ATTR_STYLE, cssText);
2047 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *startNode, NodeImpl *endNode)
2049 StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
2050 int exceptionCode = 0;
2053 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
2055 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
2056 ElementImpl *fontElement = createFontElement(document());
2057 ASSERT(exceptionCode == 0);
2058 insertNodeBefore(fontElement, startNode);
2059 if (styleChange.applyFontColor())
2060 fontElement->setAttribute(ATTR_COLOR, styleChange.fontColor());
2061 if (styleChange.applyFontFace())
2062 fontElement->setAttribute(ATTR_FACE, styleChange.fontFace());
2063 if (styleChange.applyFontSize())
2064 fontElement->setAttribute(ATTR_SIZE, styleChange.fontSize());
2065 surroundNodeRangeWithElement(startNode, endNode, fontElement);
2068 if (styleChange.cssStyle().length() > 0) {
2069 ElementImpl *styleElement = createStyleSpanElement(document());
2070 styleElement->ref();
2071 styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
2072 insertNodeBefore(styleElement, startNode);
2073 styleElement->deref();
2074 surroundNodeRangeWithElement(startNode, endNode, styleElement);
2077 if (styleChange.applyBold()) {
2078 ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
2079 ASSERT(exceptionCode == 0);
2080 insertNodeBefore(boldElement, startNode);
2081 surroundNodeRangeWithElement(startNode, endNode, boldElement);
2084 if (styleChange.applyItalic()) {
2085 ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
2086 ASSERT(exceptionCode == 0);
2087 insertNodeBefore(italicElement, startNode);
2088 surroundNodeRangeWithElement(startNode, endNode, italicElement);
2092 float ApplyStyleCommand::computedFontSize(const NodeImpl *node)
2099 Position pos(const_cast<NodeImpl *>(node), 0);
2100 CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
2103 computedStyle->ref();
2105 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE));
2108 size = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
2112 computedStyle->deref();
2116 void ApplyStyleCommand::joinChildTextNodes(NodeImpl *node, const Position &start, const Position &end)
2121 Position newStart = start;
2122 Position newEnd = end;
2124 NodeImpl *child = node->firstChild();
2126 NodeImpl *next = child->nextSibling();
2127 if (child->isTextNode() && next && next->isTextNode()) {
2128 TextImpl *childText = static_cast<TextImpl *>(child);
2129 TextImpl *nextText = static_cast<TextImpl *>(next);
2130 if (next == start.node())
2131 newStart = Position(childText, childText->length() + start.offset());
2132 if (next == end.node())
2133 newEnd = Position(childText, childText->length() + end.offset());
2134 DOMString textToMove = nextText->data();
2135 insertTextIntoNode(childText, childText->length(), textToMove);
2137 // don't move child node pointer. it may want to merge with more text nodes.
2140 child = child->nextSibling();
2144 setEndingSelection(Selection(newStart, SEL_DEFAULT_AFFINITY, newEnd, SEL_DEFAULT_AFFINITY));
2147 //------------------------------------------------------------------------------------------
2148 // DeleteFromTextNodeCommand
2150 DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(DocumentImpl *document, TextImpl *node, long offset, long count)
2151 : EditCommand(document), m_node(node), m_offset(offset), m_count(count)
2154 ASSERT(m_offset >= 0);
2155 ASSERT(m_offset < (long)m_node->length());
2156 ASSERT(m_count >= 0);
2161 DeleteFromTextNodeCommand::~DeleteFromTextNodeCommand()
2167 void DeleteFromTextNodeCommand::doApply()
2171 int exceptionCode = 0;
2172 m_text = m_node->substringData(m_offset, m_count, exceptionCode);
2173 ASSERT(exceptionCode == 0);
2175 m_node->deleteData(m_offset, m_count, exceptionCode);
2176 ASSERT(exceptionCode == 0);
2179 void DeleteFromTextNodeCommand::doUnapply()
2182 ASSERT(!m_text.isEmpty());
2184 int exceptionCode = 0;
2185 m_node->insertData(m_offset, m_text, exceptionCode);
2186 ASSERT(exceptionCode == 0);
2189 //------------------------------------------------------------------------------------------
2190 // DeleteSelectionCommand
2192 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, bool smartDelete, bool mergeBlocksAfterDelete)
2193 : CompositeEditCommand(document),
2194 m_hasSelectionToDelete(false),
2195 m_smartDelete(smartDelete),
2196 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
2204 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
2205 : CompositeEditCommand(document),
2206 m_hasSelectionToDelete(true),
2207 m_smartDelete(smartDelete),
2208 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
2209 m_selectionToDelete(selection),
2217 void DeleteSelectionCommand::initializePositionData()
2220 // Handle setting some basic positions
2222 Position start = m_selectionToDelete.start();
2223 start = positionOutsideContainingSpecialElement(start);
2224 Position end = m_selectionToDelete.end();
2225 end = positionOutsideContainingSpecialElement(end);
2227 m_upstreamStart = positionBeforePossibleContainingSpecialElement(start.upstream());
2228 m_downstreamStart = positionBeforePossibleContainingSpecialElement(start.downstream());
2229 m_upstreamEnd = positionAfterPossibleContainingSpecialElement(end.upstream());
2230 m_downstreamEnd = positionAfterPossibleContainingSpecialElement(end.downstream());
2233 // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
2235 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity());
2236 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
2238 if (m_smartDelete) {
2240 // skip smart delete if the selection to delete already starts or ends with whitespace
2241 Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()).deepEquivalent();
2242 bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
2243 if (!skipSmartDelete)
2244 skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
2246 // extend selection upstream if there is whitespace there
2247 bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity(), true).isNotNull();
2248 if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
2249 VisiblePosition visiblePos = VisiblePosition(start, m_selectionToDelete.startAffinity()).previous();
2250 pos = visiblePos.deepEquivalent();
2251 // Expand out one character upstream for smart delete and recalculate
2252 // positions based on this change.
2253 m_upstreamStart = pos.upstream();
2254 m_downstreamStart = pos.downstream();
2255 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
2258 // trailing whitespace is only considered for smart delete if there is no leading
2259 // whitespace, as in the case where you double-click the first word of a paragraph.
2260 if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
2261 // Expand out one character downstream for smart delete and recalculate
2262 // positions based on this change.
2263 pos = VisiblePosition(end, m_selectionToDelete.endAffinity()).next().deepEquivalent();
2264 m_upstreamEnd = pos.upstream();
2265 m_downstreamEnd = pos.downstream();
2266 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
2270 m_trailingWhitespaceValid = true;
2273 // Handle setting start and end blocks and the start node.
2275 m_startBlock = m_downstreamStart.node()->enclosingBlockFlowElement();
2276 m_startBlock->ref();
2277 m_endBlock = m_upstreamEnd.node()->enclosingBlockFlowElement();
2279 m_startNode = m_upstreamStart.node();
2283 // Handle detecting if the line containing the selection end is itself fully selected.
2284 // This is one of the tests that determines if block merging of content needs to be done.
2286 VisiblePosition visibleEnd(end, m_selectionToDelete.endAffinity());
2287 if (isStartOfParagraph(visibleEnd) || isEndOfParagraph(visibleEnd)) {
2288 Position previousLineStart = previousLinePosition(visibleEnd, 0).deepEquivalent();
2289 if (previousLineStart.isNull() || RangeImpl::compareBoundaryPoints(previousLineStart, m_downstreamStart) >= 0)
2290 m_mergeBlocksAfterDelete = false;
2293 debugPosition("m_upstreamStart ", m_upstreamStart);
2294 debugPosition("m_downstreamStart ", m_downstreamStart);
2295 debugPosition("m_upstreamEnd ", m_upstreamEnd);
2296 debugPosition("m_downstreamEnd ", m_downstreamEnd);
2297 debugPosition("m_leadingWhitespace ", m_leadingWhitespace);
2298 debugPosition("m_trailingWhitespace ", m_trailingWhitespace);
2299 debugNode( "m_startBlock ", m_startBlock);
2300 debugNode( "m_endBlock ", m_endBlock);
2301 debugNode( "m_startNode ", m_startNode);
2304 void DeleteSelectionCommand::insertPlaceholderForAncestorBlockContent()
2306 // This code makes sure a line does not disappear when deleting in this case:
2307 // <p>foo</p>bar<p>baz</p>
2308 // Select "bar" and hit delete. If nothing is done, the line containing bar will disappear.
2309 // It needs to be held open by inserting a placeholder.
2311 // <rdar://problem/3928305> selecting an entire line and typing over causes new inserted text at top of document
2313 // The checks below detect the case where the selection contains content in an ancestor block
2314 // surrounded by child blocks.
2316 VisiblePosition visibleStart(m_upstreamStart, VP_DEFAULT_AFFINITY);
2317 VisiblePosition beforeStart = visibleStart.previous();
2318 NodeImpl *startBlock = enclosingBlockFlowElement(visibleStart);
2319 NodeImpl *beforeStartBlock = enclosingBlockFlowElement(beforeStart);
2321 if (!beforeStart.isNull() &&
2322 !inSameBlock(visibleStart, beforeStart) &&
2323 beforeStartBlock->isAncestor(startBlock) &&
2324 startBlock != m_upstreamStart.node()) {
2326 VisiblePosition visibleEnd(m_downstreamEnd, VP_DEFAULT_AFFINITY);
2327 VisiblePosition afterEnd = visibleEnd.next();
2329 if ((!afterEnd.isNull() && !inSameBlock(afterEnd, visibleEnd) && !inSameBlock(afterEnd, visibleStart)) ||
2330 (m_downstreamEnd == m_selectionToDelete.end() && isEndOfParagraph(visibleEnd))) {
2331 NodeImpl *block = createDefaultParagraphElement(document());
2332 insertNodeBefore(block, m_upstreamStart.node());
2333 addBlockPlaceholderIfNeeded(block);
2334 m_endingPosition = Position(block, 0);
2339 void DeleteSelectionCommand::saveTypingStyleState()
2341 // Figure out the typing style in effect before the delete is done.
2342 // FIXME: Improve typing style.
2343 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2344 CSSComputedStyleDeclarationImpl *computedStyle = m_selectionToDelete.start().computedStyle();
2345 computedStyle->ref();
2346 m_typingStyle = computedStyle->copyInheritableProperties();
2347 m_typingStyle->ref();
2348 computedStyle->deref();
2351 bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
2353 // Check for special-case where the selection contains only a BR on a line by itself after another BR.
2354 bool upstreamStartIsBR = m_startNode->id() == ID_BR;
2355 bool downstreamStartIsBR = m_downstreamStart.node()->id() == ID_BR;
2356 bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
2357 if (isBROnLineByItself) {
2358 removeNode(m_downstreamStart.node());
2359 m_endingPosition = m_upstreamStart;
2360 m_mergeBlocksAfterDelete = false;
2364 // Not a special-case delete per se, but we can detect that the merging of content between blocks
2365 // should not be done.
2366 if (upstreamStartIsBR && downstreamStartIsBR)
2367 m_mergeBlocksAfterDelete = false;
2372 void DeleteSelectionCommand::setStartNode(NodeImpl *node)
2374 NodeImpl *old = m_startNode;
2382 void DeleteSelectionCommand::handleGeneralDelete()
2384 int startOffset = m_upstreamStart.offset();
2385 VisiblePosition visibleEnd = VisiblePosition(m_downstreamEnd, m_selectionToDelete.endAffinity());
2386 bool endAtEndOfBlock = isEndOfBlock(visibleEnd);
2388 // Handle some special cases where the selection begins and ends on specific visible units.
2389 // Sometimes a node that is actually selected needs to be retained in order to maintain
2390 // user expectations for the delete operation. Here is an example:
2391 // 1. Open a new Blot or Mail document
2392 // 2. hit Return ten times or so
2393 // 3. Type a letter (do not hit Return after it)
2394 // 4. Type shift-up-arrow to select the line containing the letter and the previous blank line
2396 // You expect the insertion point to wind up at the start of the line where your selection began.
2397 // Because of the nature of HTML, the editing code needs to perform a special check to get
2398 // this behavior. So:
2399 // If the entire start block is selected, and the selection does not extend to the end of the
2400 // end of a block other than the block containing the selection start, then do not delete the
2401 // start block, otherwise delete the start block.
2402 // A similar case is provided to cover selections starting in BR elements.
2403 if (startOffset == 1 && m_startNode && m_startNode->id() == ID_BR) {
2404 setStartNode(m_startNode->traverseNextNode());
2407 if (m_startBlock != m_endBlock && startOffset == 0 && m_startNode && m_startNode->id() == ID_BR && endAtEndOfBlock) {
2408 // Don't delete the BR element
2409 setStartNode(m_startNode->traverseNextNode());
2411 else if (m_startBlock != m_endBlock && isStartOfBlock(VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()))) {
2412 if (!m_startBlock->isAncestor(m_endBlock) && !isStartOfBlock(visibleEnd) && endAtEndOfBlock) {
2413 // Delete all the children of the block, but not the block itself.
2414 setStartNode(m_startBlock->firstChild());
2418 else if (startOffset >= m_startNode->caretMaxOffset() &&
2419 (m_startNode->isAtomicNode() || startOffset == 0)) {
2420 // Move the start node to the next node in the tree since the startOffset is equal to
2421 // or beyond the start node's caretMaxOffset This means there is nothing visible to delete.
2422 // But don't do this if the node is not atomic - we don't want to move into the first child.
2424 // Also, before moving on, delete any insignificant text that may be present in a text node.
2425 if (m_startNode->isTextNode()) {
2426 // Delete any insignificant text from this node.
2427 TextImpl *text = static_cast<TextImpl *>(m_startNode);
2428 if (text->length() > (unsigned)m_startNode->caretMaxOffset())
2429 deleteTextFromNode(text, m_startNode->caretMaxOffset(), text->length() - m_startNode->caretMaxOffset());
2432 // shift the start node to the next
2433 setStartNode(m_startNode->traverseNextNode());
2437 // Done adjusting the start. See if we're all done.
2441 if (m_startNode == m_downstreamEnd.node()) {
2442 // The selection to delete is all in one node.
2443 if (!m_startNode->renderer() ||
2444 (startOffset == 0 && m_downstreamEnd.offset() >= maxDeepOffset(m_startNode))) {
2446 removeFullySelectedNode(m_startNode);
2447 } else if (m_downstreamEnd.offset() - startOffset > 0) {
2448 if (m_startNode->isTextNode()) {
2449 // in a text node that needs to be trimmed
2450 TextImpl *text = static_cast<TextImpl *>(m_startNode);
2451 deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset);
2452 m_trailingWhitespaceValid = false;
2454 removeChildrenInRange(m_startNode, startOffset, m_downstreamEnd.offset());
2455 m_endingPosition = m_upstreamStart;
2460 // The selection to delete spans more than one node.
2461 NodeImpl *node = m_startNode;
2463 if (startOffset > 0) {
2464 if (m_startNode->isTextNode()) {
2465 // in a text node that needs to be trimmed
2466 TextImpl *text = static_cast<TextImpl *>(node);
2467 deleteTextFromNode(text, startOffset, text->length() - startOffset);
2468 node = node->traverseNextNode();
2470 node = m_startNode->childNode(startOffset);
2474 // handle deleting all nodes that are completely selected
2475 while (node && node != m_downstreamEnd.node()) {
2476 if (RangeImpl::compareBoundaryPoints(Position(node, 0), m_downstreamEnd) >= 0) {
2477 // traverseNextSibling just blew past the end position, so stop deleting
2479 } else if (!m_downstreamEnd.node()->isAncestor(node)) {
2480 NodeImpl *nextNode = node->traverseNextSibling();
2481 // if we just removed a node from the end container, update end position so the
2482 // check above will work
2483 if (node->parentNode() == m_downstreamEnd.node()) {
2484 ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.offset());
2485 m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.offset() - 1);
2487 removeFullySelectedNode(node);
2490 NodeImpl *n = node->lastChild();
2491 while (n && n->lastChild())
2493 if (n == m_downstreamEnd.node() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMaxOffset()) {
2494 removeFullySelectedNode(node);
2495 m_trailingWhitespaceValid = false;
2499 node = node->traverseNextNode();
2505 if (m_downstreamEnd.node() != m_startNode && !m_upstreamStart.node()->isAncestor(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMinOffset()) {
2506 if (m_downstreamEnd.offset() >= maxDeepOffset(m_downstreamEnd.node())) {
2507 // need to delete whole node
2508 // we can get here if this is the last node in the block
2509 // remove an ancestor of m_downstreamEnd.node(), and thus m_downstreamEnd.node() itself
2510 if (!m_upstreamStart.node()->inDocument() ||
2511 m_upstreamStart.node() == m_downstreamEnd.node() ||
2512 m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
2513 m_upstreamStart = Position(m_downstreamEnd.node()->parentNode(), m_downstreamEnd.node()->nodeIndex());
2516 removeFullySelectedNode(m_downstreamEnd.node());
2517 m_trailingWhitespaceValid = false;
2519 if (m_downstreamEnd.node()->isTextNode()) {
2520 // in a text node that needs to be trimmed
2521 TextImpl *text = static_cast<TextImpl *>(m_downstreamEnd.node());
2522 if (m_downstreamEnd.offset() > 0) {
2523 deleteTextFromNode(text, 0, m_downstreamEnd.offset());
2524 m_downstreamEnd = Position(text, 0);
2525 m_trailingWhitespaceValid = false;
2529 if (m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
2530 NodeImpl *n = m_upstreamStart.node();
2531 while (n && n->parentNode() != m_downstreamEnd.node())
2532 n = n->parentNode();
2534 offset = n->nodeIndex() + 1;
2536 removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.offset());
2537 m_downstreamEnd = Position(m_downstreamEnd.node(), offset);
2544 void DeleteSelectionCommand::fixupWhitespace()
2546 document()->updateLayout();
2547 if (m_leadingWhitespace.isNotNull() && (m_trailingWhitespace.isNotNull() || !m_leadingWhitespace.isRenderedCharacter())) {
2548 LOG(Editing, "replace leading");
2549 TextImpl *textNode = static_cast<TextImpl *>(m_leadingWhitespace.node());
2550 replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
2552 else if (m_trailingWhitespace.isNotNull()) {
2553 if (m_trailingWhitespaceValid) {
2554 if (!m_trailingWhitespace.isRenderedCharacter()) {
2555 LOG(Editing, "replace trailing [valid]");
2556 TextImpl *textNode = static_cast<TextImpl *>(m_trailingWhitespace.node());
2557 replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
2561 Position pos = m_endingPosition.downstream();
2562 pos = Position(pos.node(), pos.offset() - 1);
2563 if (nextCharacterIsCollapsibleWhitespace(pos) && !pos.isRenderedCharacter()) {
2564 LOG(Editing, "replace trailing [invalid]");
2565 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
2566 replaceTextInNode(textNode, pos.offset(), 1, nonBreakingSpaceString());
2567 // need to adjust ending position since the trailing position is not valid.
2568 m_endingPosition = pos;
2574 // This function moves nodes in the block containing startNode to dstBlock, starting
2575 // from startNode and proceeding to the end of the paragraph. Nodes in the block containing
2576 // startNode that appear in document order before startNode are not moved.
2577 // This function is an important helper for deleting selections that cross paragraph
2579 void DeleteSelectionCommand::moveNodesAfterNode()
2581 if (!m_mergeBlocksAfterDelete)
2584 if (m_endBlock == m_startBlock)
2587 NodeImpl *startNode = m_downstreamEnd.node();
2588 NodeImpl *dstNode = m_upstreamStart.node();
2590 if (!startNode->inDocument() || !dstNode->inDocument())
2593 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
2594 if (isTableStructureNode(startBlock) || isListStructureNode(startBlock))
2595 // Do not move content between parts of a table or list.
2598 // Now that we are about to add content, check to see if a placeholder element
2600 removeBlockPlaceholder(startBlock);
2602 // Move the subtree containing node
2603 NodeImpl *node = startNode->enclosingInlineElement();
2605 // Insert after the subtree containing destNode
2606 NodeImpl *refNode = dstNode->enclosingInlineElement();
2608 // Nothing to do if start is already at the beginning of dstBlock
2609 NodeImpl *dstBlock = refNode->enclosingBlockFlowElement();
2610 if (startBlock == dstBlock->firstChild())
2614 NodeImpl *rootNode = refNode->rootEditableElement();
2615 while (node && node->isAncestor(startBlock)) {
2616 NodeImpl *moveNode = node;
2617 node = node->nextSibling();
2618 removeNode(moveNode);
2619 if (moveNode->id() == ID_BR && !moveNode->renderer()) {
2620 // Just remove this node, and don't put it back.
2621 // If the BR was not rendered (since it was at the end of a block, for instance),
2622 // putting it back in the document might make it appear, and that is not desirable.
2625 if (refNode == rootNode)
2626 insertNodeAt(moveNode, refNode, 0);
2628 insertNodeAfter(moveNode, refNode);
2630 if (moveNode->id() == ID_BR)
2634 // If the startBlock no longer has any kids, we may need to deal with adding a BR
2635 // to make the layout come out right. Consider this document:
2641 // Placing the insertion before before the 'T' of 'Two' and hitting delete will
2642 // move the contents of the div to the block containing 'One' and delete the div.
2643 // This will have the side effect of moving 'Three' on to the same line as 'One'
2644 // and 'Two'. This is undesirable. We fix this up by adding a BR before the 'Three'.
2645 // This may not be ideal, but it is better than nothing.
2646 document()->updateLayout();
2647 if (!startBlock->renderer() || !startBlock->renderer()->firstChild()) {
2648 removeNode(startBlock);
2649 document()->updateLayout();
2650 if (refNode->renderer() && refNode->renderer()->inlineBox() && refNode->renderer()->inlineBox()->nextOnLineExists()) {
2651 insertNodeAfter(createBreakElement(document()), refNode);
2656 void DeleteSelectionCommand::calculateEndingPosition()
2658 if (m_endingPosition.isNotNull() && m_endingPosition.node()->inDocument())
2661 m_endingPosition = m_upstreamStart;
2662 if (m_endingPosition.node()->inDocument())
2665 m_endingPosition = m_downstreamEnd;
2666 if (m_endingPosition.node()->inDocument())
2669 m_endingPosition = Position(m_startBlock, 0);
2670 if (m_endingPosition.node()->inDocument())
2673 m_endingPosition = Position(m_endBlock, 0);
2674 if (m_endingPosition.node()->inDocument())
2677 m_endingPosition = Position(document()->documentElement(), 0);
2680 void DeleteSelectionCommand::calculateTypingStyleAfterDelete(NodeImpl *insertedPlaceholder)
2682 // Compute the difference between the style before the delete and the style now
2683 // after the delete has been done. Set this style on the part, so other editing
2684 // commands being composed with this one will work, and also cache it on the command,
2685 // so the KHTMLPart::appliedEditing can set it after the whole composite command
2687 // FIXME: Improve typing style.
2688 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2689 CSSComputedStyleDeclarationImpl endingStyle(m_endingPosition.node());
2690 endingStyle.diff(m_typingStyle);
2691 if (!m_typingStyle->length()) {
2692 m_typingStyle->deref();
2695 if (insertedPlaceholder && m_typingStyle) {
2696 // Apply style to the placeholder. This makes sure that the single line in the
2697 // paragraph has the right height, and that the paragraph takes on the style
2698 // of the preceding line and retains it even if you click away, click back, and
2699 // then start typing. In this case, the typing style is applied right now, and
2700 // is not retained until the next typing action.
2702 // FIXME: is this even right? I don't think post-deletion typing style is supposed
2703 // to be saved across clicking away and clicking back, it certainly isn't in TextEdit
2705 Position pastPlaceholder(insertedPlaceholder, 1);
2707 setEndingSelection(Selection(m_endingPosition, m_selectionToDelete.endAffinity(), pastPlaceholder, DOWNSTREAM));
2709 applyStyle(m_typingStyle, EditActionUnspecified);
2711 m_typingStyle->deref();
2714 // Set m_typingStyle as the typing style.
2715 // It's perfectly OK for m_typingStyle to be null.
2716 document()->part()->setTypingStyle(m_typingStyle);
2717 setTypingStyle(m_typingStyle);
2720 void DeleteSelectionCommand::clearTransientState()
2722 m_selectionToDelete.clear();
2723 m_upstreamStart.clear();
2724 m_downstreamStart.clear();
2725 m_upstreamEnd.clear();
2726 m_downstreamEnd.clear();
2727 m_endingPosition.clear();
2728 m_leadingWhitespace.clear();
2729 m_trailingWhitespace.clear();
2732 m_startBlock->deref();
2736 m_endBlock->deref();
2740 m_startNode->deref();
2743 if (m_typingStyle) {
2744 m_typingStyle->deref();
2749 void DeleteSelectionCommand::doApply()
2751 // If selection has not been set to a custom selection when the command was created,
2752 // use the current ending selection.
2753 if (!m_hasSelectionToDelete)
2754 m_selectionToDelete = endingSelection();
2756 if (!m_selectionToDelete.isRange())
2759 // save this to later make the selection with
2760 EAffinity affinity = m_selectionToDelete.startAffinity();
2763 initializePositionData();
2765 if (!m_startBlock || !m_endBlock) {
2766 // Can't figure out what blocks we're in. This can happen if
2767 // the document structure is not what we are expecting, like if
2768 // the document has no body element, or if the editable block
2769 // has been changed to display: inline. Some day it might
2770 // be nice to be able to deal with this, but for now, bail.
2771 clearTransientState();
2775 // if all we are deleting is complete paragraph(s), we need to make
2776 // sure a blank paragraph remains when we are done
2777 bool forceBlankParagraph = isStartOfParagraph(VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY)) &&
2778 isEndOfParagraph(VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY));
2780 // Delete any text that may hinder our ability to fixup whitespace after the detele
2781 deleteInsignificantTextDownstream(m_trailingWhitespace);
2783 saveTypingStyleState();
2784 insertPlaceholderForAncestorBlockContent();
2786 if (!handleSpecialCaseBRDelete())
2787 handleGeneralDelete();
2789 // Do block merge if start and end of selection are in different blocks.
2790 moveNodesAfterNode();
2792 calculateEndingPosition();
2795 // if the m_endingPosition is already a blank paragraph, there is
2796 // no need to force a new one
2797 if (forceBlankParagraph &&
2798 isStartOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY)) &&
2799 isEndOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY))) {
2800 forceBlankParagraph = false;
2803 NodeImpl *addedPlaceholder = forceBlankParagraph ? insertBlockPlaceholder(m_endingPosition) :
2804 addBlockPlaceholderIfNeeded(m_endingPosition.node());
2806 calculateTypingStyleAfterDelete(addedPlaceholder);
2807 debugPosition("endingPosition ", m_endingPosition);
2808 setEndingSelection(Selection(m_endingPosition, affinity));
2809 clearTransientState();
2810 rebalanceWhitespace();
2813 EditAction DeleteSelectionCommand::editingAction() const
2815 // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
2816 // but in that case there's a TypingCommand that supplies the editingAction(), so
2817 // the Undo menu correctly shows "Undo Typing"
2818 return EditActionCut;
2821 bool DeleteSelectionCommand::preservesTypingStyle() const
2826 //------------------------------------------------------------------------------------------
2827 // InsertIntoTextNode
2829 InsertIntoTextNode::InsertIntoTextNode(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
2830 : EditCommand(document), m_node(node), m_offset(offset)
2833 ASSERT(m_offset >= 0);
2834 ASSERT(!text.isEmpty());
2837 m_text = text.copy(); // make a copy to ensure that the string never changes
2840 InsertIntoTextNode::~InsertIntoTextNode()
2846 void InsertIntoTextNode::doApply()
2849 ASSERT(m_offset >= 0);
2850 ASSERT(!m_text.isEmpty());
2852 int exceptionCode = 0;
2853 m_node->insertData(m_offset, m_text, exceptionCode);
2854 ASSERT(exceptionCode == 0);
2857 void InsertIntoTextNode::doUnapply()
2860 ASSERT(m_offset >= 0);
2861 ASSERT(!m_text.isEmpty());
2863 int exceptionCode = 0;
2864 m_node->deleteData(m_offset, m_text.length(), exceptionCode);
2865 ASSERT(exceptionCode == 0);
2868 //------------------------------------------------------------------------------------------
2869 // InsertLineBreakCommand
2871 InsertLineBreakCommand::InsertLineBreakCommand(DocumentImpl *document)
2872 : CompositeEditCommand(document)
2876 bool InsertLineBreakCommand::preservesTypingStyle() const
2881 void InsertLineBreakCommand::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
2883 // Insert the BR after the caret position. In the case the
2884 // position is a block, do an append. We don't want to insert
2885 // the BR *after* the block.
2886 Position upstream(pos.upstream());
2887 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
2888 if (cb == pos.node())
2889 appendNode(node, cb);
2891 insertNodeAfter(node, pos.node());
2894 void InsertLineBreakCommand::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
2896 // Insert the BR after the caret position. In the case the
2897 // position is a block, do an append. We don't want to insert
2898 // the BR *before* the block.
2899 Position upstream(pos.upstream());
2900 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
2901 if (cb == pos.node())
2902 appendNode(node, cb);
2904 insertNodeBefore(node, pos.node());
2907 void InsertLineBreakCommand::doApply()
2910 Selection selection = endingSelection();
2912 ElementImpl *breakNode = createBreakElement(document());
2913 NodeImpl *nodeToInsert = breakNode;
2915 Position pos(selection.start().upstream());
2917 pos = positionOutsideContainingSpecialElement(pos);
2919 bool atStart = pos.offset() <= pos.node()->caretMinOffset();
2920 bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
2921 bool atEndOfBlock = isEndOfBlock(VisiblePosition(pos, selection.startAffinity()));
2924 LOG(Editing, "input newline case 1");
2925 // Check for a trailing BR. If there isn't one, we'll need to insert an "extra" one.
2926 // This makes the "real" BR we want to insert appear in the rendering without any
2927 // significant side effects (and no real worries either since you can't arrow past
2929 if (pos.node()->id() == ID_BR && pos.offset() == 0) {
2930 // Already placed in a trailing BR. Insert "real" BR before it and leave the selection alone.
2931 insertNodeBefore(nodeToInsert, pos.node());
2934 NodeImpl *next = pos.node()->traverseNextNode();
2935 bool hasTrailingBR = next && next->id() == ID_BR && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
2936 insertNodeAfterPosition(nodeToInsert, pos);
2937 if (hasTrailingBR) {
2938 setEndingSelection(Selection(Position(next, 0), DOWNSTREAM));
2940 else if (!document()->inStrictMode()) {
2941 // Insert an "extra" BR at the end of the block.
2942 ElementImpl *extraBreakNode = createBreakElement(document());
2943 insertNodeAfter(extraBreakNode, nodeToInsert);
2944 setEndingSelection(Position(extraBreakNode, 0), DOWNSTREAM);
2949 LOG(Editing, "input newline case 2");
2950 // Insert node before downstream position, and place caret there as well.
2951 Position endingPosition = pos.downstream();
2952 insertNodeBeforePosition(nodeToInsert, endingPosition);
2953 setEndingSelection(endingPosition, DOWNSTREAM);
2956 LOG(Editing, "input newline case 3");
2957 // Insert BR after this node. Place caret in the position that is downstream
2958 // of the current position, reckoned before inserting the BR in between.
2959 Position endingPosition = pos.downstream();
2960 insertNodeAfterPosition(nodeToInsert, pos);
2961 setEndingSelection(endingPosition, DOWNSTREAM);
2964 // Split a text node
2965 LOG(Editing, "input newline case 4");
2966 ASSERT(pos.node()->isTextNode());
2969 int exceptionCode = 0;
2970 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
2971 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
2972 deleteTextFromNode(textNode, 0, pos.offset());
2973 insertNodeBefore(textBeforeNode, textNode);
2974 insertNodeBefore(nodeToInsert, textNode);
2975 Position endingPosition = Position(textNode, 0);
2977 // Handle whitespace that occurs after the split
2978 document()->updateLayout();
2979 if (!endingPosition.isRenderedCharacter()) {
2980 // Clear out all whitespace and insert one non-breaking space
2981 deleteInsignificantTextDownstream(endingPosition);
2982 insertTextIntoNode(textNode, 0, nonBreakingSpaceString());
2985 setEndingSelection(endingPosition, DOWNSTREAM);
2988 // Handle the case where there is a typing style.
2989 // FIXME: Improve typing style.
2990 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2992 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
2994 if (typingStyle && typingStyle->length() > 0) {
2995 Selection selectionBeforeStyle = endingSelection();
2997 DOM::RangeImpl *rangeAroundNode = document()->createRange();
2999 rangeAroundNode->selectNode(nodeToInsert, exception);
3001 // affinity is not really important since this is a temp selection
3002 // just for calling applyStyle
3003 setEndingSelection(Selection(rangeAroundNode, khtml::SEL_DEFAULT_AFFINITY, khtml::SEL_DEFAULT_AFFINITY));
3004 applyStyle(typingStyle);
3006 setEndingSelection(selectionBeforeStyle);
3009 rebalanceWhitespace();
3012 //------------------------------------------------------------------------------------------
3013 // InsertNodeBeforeCommand
3015 InsertNodeBeforeCommand::InsertNodeBeforeCommand(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
3016 : EditCommand(document), m_insertChild(insertChild), m_refChild(refChild)
3018 ASSERT(m_insertChild);
3019 m_insertChild->ref();
3025 InsertNodeBeforeCommand::~InsertNodeBeforeCommand()
3027 ASSERT(m_insertChild);
3028 m_insertChild->deref();
3031 m_refChild->deref();
3034 void InsertNodeBeforeCommand::doApply()
3036 ASSERT(m_insertChild);
3038 ASSERT(m_refChild->parentNode());
3040 int exceptionCode = 0;
3041 m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
3042 ASSERT(exceptionCode == 0);
3045 void InsertNodeBeforeCommand::doUnapply()
3047 ASSERT(m_insertChild);
3049 ASSERT(m_refChild->parentNode());
3051 int exceptionCode = 0;
3052 m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
3053 ASSERT(exceptionCode == 0);
3056 //------------------------------------------------------------------------------------------
3057 // InsertParagraphSeparatorCommand
3059 InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(DocumentImpl *document)
3060 : CompositeEditCommand(document), m_style(0)
3064 InsertParagraphSeparatorCommand::~InsertParagraphSeparatorCommand()
3066 derefNodesInList(clonedNodes);
3071 bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
3076 ElementImpl *InsertParagraphSeparatorCommand::createParagraphElement()
3078 ElementImpl *element = createDefaultParagraphElement(document());
3080 clonedNodes.append(element);
3084 void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
3086 // It is only important to set a style to apply later if we're at the boundaries of
3087 // a paragraph. Otherwise, content that is moved as part of the work of the command
3088 // will lend their styles to the new paragraph without any extra work needed.
3089 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
3090 if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos))
3095 m_style = styleAtPosition(pos);
3099 void InsertParagraphSeparatorCommand::applyStyleAfterInsertion()
3101 // FIXME: Improve typing style.
3102 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3106 CSSComputedStyleDeclarationImpl endingStyle(endingSelection().start().node());
3107 endingStyle.diff(m_style);
3108 if (m_style->length() > 0) {
3109 applyStyle(m_style);
3113 void InsertParagraphSeparatorCommand::doApply()
3115 bool splitText = false;
3116 Selection selection = endingSelection();
3117 if (selection.isNone())
3120 Position pos = selection.start();
3121 EAffinity affinity = selection.startAffinity();
3123 // Delete the current selection.
3124 if (selection.isRange()) {
3125 calculateStyleBeforeInsertion(pos);
3126 deleteSelection(false, false);
3127 pos = endingSelection().start();
3128 affinity = endingSelection().startAffinity();
3131 pos = positionOutsideContainingSpecialElement(pos);
3133 calculateStyleBeforeInsertion(pos);
3135 // Find the start block.
3136 NodeImpl *startNode = pos.node();
3137 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
3138 if (!startBlock || !startBlock->parentNode())
3141 VisiblePosition visiblePos(pos, affinity);
3142 bool isFirstInBlock = isStartOfBlock(visiblePos);
3143 bool isLastInBlock = isEndOfBlock(visiblePos);
3144 bool startBlockIsRoot = startBlock == startBlock->rootEditableElement();
3146 // This is the block that is going to be inserted.
3147 NodeImpl *blockToInsert = startBlockIsRoot ? createParagraphElement() : startBlock->cloneNode(false);
3149 //---------------------------------------------------------------------
3150 // Handle empty block case.
3151 if (isFirstInBlock && isLastInBlock) {
3152 LOG(Editing, "insert paragraph separator: empty block case");
3153 if (startBlockIsRoot) {
3154 NodeImpl *extraBlock = createParagraphElement();
3155 appendNode(extraBlock, startBlock);
3156 appendBlockPlaceholder(extraBlock);
3157 appendNode(blockToInsert, startBlock);
3160 insertNodeAfter(blockToInsert, startBlock);
3162 appendBlockPlaceholder(blockToInsert);
3163 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
3164 applyStyleAfterInsertion();
3168 //---------------------------------------------------------------------
3169 // Handle case when position is in the last visible position in its block.
3170 if (isLastInBlock) {
3171 LOG(Editing, "insert paragraph separator: last in block case");
3172 if (startBlockIsRoot)
3173 appendNode(blockToInsert, startBlock);
3175 insertNodeAfter(blockToInsert, startBlock);
3176 appendBlockPlaceholder(blockToInsert);
3177 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
3178 applyStyleAfterInsertion();
3182 //---------------------------------------------------------------------
3183 // Handle case when position is in the first visible position in its block.
3184 // and similar case where upstream position is in another block.
3185 bool prevInDifferentBlock = !inSameBlock(visiblePos, visiblePos.previous());
3187 if (prevInDifferentBlock || isFirstInBlock) {
3188 LOG(Editing, "insert paragraph separator: first in block case");
3189 pos = pos.downstream();
3190 pos = positionOutsideContainingSpecialElement(pos);
3193 if (isFirstInBlock && !startBlockIsRoot) {
3194 refNode = startBlock;
3195 } else if (pos.node() == startBlock && startBlockIsRoot) {
3196 ASSERT(startBlock->childNode(pos.offset())); // must be true or we'd be in the end of block case
3197 refNode = startBlock->childNode(pos.offset());
3199 refNode = pos.node();
3202 insertNodeBefore(blockToInsert, refNode);
3203 appendBlockPlaceholder(blockToInsert);
3204 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
3205 applyStyleAfterInsertion();
3206 setEndingSelection(pos, DOWNSTREAM);
3210 //---------------------------------------------------------------------
3211 // Handle the (more complicated) general case,
3213 LOG(Editing, "insert paragraph separator: general case");
3215 // Check if pos.node() is a <br>. If it is, and the document is in quirks mode,
3216 // then this <br> will collapse away when we add a block after it. Add an extra <br>.
3217 if (!document()->inStrictMode()) {
3218 Position upstreamPos = pos.upstream();
3219 if (upstreamPos.node()->id() == ID_BR)
3220 insertNodeAfter(createBreakElement(document()), upstreamPos.node());
3223 // Move downstream. Typing style code will take care of carrying along the
3224 // style of the upstream position.
3225 pos = pos.downstream();
3226 startNode = pos.node();
3228 // Build up list of ancestors in between the start node and the start block.
3229 if (startNode != startBlock) {
3230 for (NodeImpl *n = startNode->parentNode(); n && n != startBlock; n = n->parentNode())
3231 ancestors.prepend(n);
3234 // Make sure we do not cause a rendered space to become unrendered.
3235 // FIXME: We need the affinity for pos, but pos.downstream() does not give it
3236 Position leadingWhitespace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
3237 if (leadingWhitespace.isNotNull()) {
3238 TextImpl *textNode = static_cast<TextImpl *>(leadingWhitespace.node());
3239 replaceTextInNode(textNode, leadingWhitespace.offset(), 1, nonBreakingSpaceString());
3242 // Split at pos if in the middle of a text node.
3243 if (startNode->isTextNode()) {
3244 TextImpl *textNode = static_cast<TextImpl *>(startNode);
3245 bool atEnd = (unsigned long)pos.offset() >= textNode->length();
3246 if (pos.offset() > 0 && !atEnd) {
3247 splitTextNode(textNode, pos.offset());
3248 pos = Position(startNode, 0);
3253 // Put the added block in the tree.
3254 if (startBlockIsRoot) {
3255 appendNode(blockToInsert, startBlock);
3257 insertNodeAfter(blockToInsert, startBlock);
3260 // Make clones of ancestors in between the start node and the start block.
3261 NodeImpl *parent = blockToInsert;
3262 for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
3263 NodeImpl *child = it.current()->cloneNode(false); // shallow clone
3265 clonedNodes.append(child);
3266 appendNode(child, parent);
3270 // Insert a block placeholder if the next visible position is in a different paragraph,
3271 // because we know that there will be no content on the first line of the new block
3272 // before the first block child. So, we need the placeholder to "hold the first line open".
3273 VisiblePosition next = visiblePos.next();
3274 if (!next.isNull() && !inSameBlock(visiblePos, next))
3275 appendBlockPlaceholder(blockToInsert);
3277 // Move the start node and the siblings of the start node.
3278 if (startNode != startBlock) {
3279 NodeImpl *n = startNode;
3280 if (pos.offset() >= startNode->caretMaxOffset()) {
3281 n = startNode->nextSibling();
3283 while (n && n != blockToInsert) {
3284 NodeImpl *next = n->nextSibling();
3286 appendNode(n, parent);
3291 // Move everything after the start node.
3292 NodeImpl *leftParent = ancestors.last();
3293 while (leftParent && leftParent != startBlock) {
3294 parent = parent->parentNode();
3295 NodeImpl *n = leftParent->nextSibling();
3296 while (n && n != blockToInsert) {
3297 NodeImpl *next = n->nextSibling();
3299 appendNode(n, parent);
3302 leftParent = leftParent->parentNode();
3305 // Handle whitespace that occurs after the split
3307 document()->updateLayout();
3308 pos = Position(startNode, 0);
3309 if (!pos.isRenderedCharacter()) {
3310 // Clear out all whitespace and insert one non-breaking space
3311 ASSERT(startNode && startNode->isTextNode());
3312 deleteInsignificantTextDownstream(pos);
3313 insertTextIntoNode(static_cast<TextImpl *>(startNode), 0, nonBreakingSpaceString());
3317 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
3318 rebalanceWhitespace();
3319 applyStyleAfterInsertion();
3322 //------------------------------------------------------------------------------------------
3323 // InsertParagraphSeparatorInQuotedContentCommand
3325 InsertParagraphSeparatorInQuotedContentCommand::InsertParagraphSeparatorInQuotedContentCommand(DocumentImpl *document)
3326 : CompositeEditCommand(document), m_breakNode(0)
3330 InsertParagraphSeparatorInQuotedContentCommand::~InsertParagraphSeparatorInQuotedContentCommand()
3332 derefNodesInList(clonedNodes);
3334 m_breakNode->deref();
3337 void InsertParagraphSeparatorInQuotedContentCommand::doApply()
3339 Selection selection = endingSelection();
3340 if (selection.isNone())
3343 // Delete the current selection.
3344 Position pos = selection.start();
3345 EAffinity affinity = selection.startAffinity();
3346 if (selection.isRange()) {
3347 deleteSelection(false, false);
3348 pos = endingSelection().start().upstream();
3349 affinity = endingSelection().startAffinity();
3352 // Find the top-most blockquote from the start.
3353 NodeImpl *startNode = pos.node();
3354 NodeImpl *topBlockquote = 0;
3355 for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
3356 if (isMailBlockquote(n))
3359 if (!topBlockquote || !topBlockquote->parentNode())
3362 // Insert a break after the top blockquote.
3363 m_breakNode = createBreakElement(document());
3365 insertNodeAfter(m_breakNode, topBlockquote);
3367 if (!isLastVisiblePositionInNode(VisiblePosition(pos, affinity), topBlockquote)) {
3369 NodeImpl *newStartNode = 0;
3370 // Split at pos if in the middle of a text node.
3371 if (startNode->isTextNode()) {
3372 TextImpl *textNode = static_cast<TextImpl *>(startNode);
3373 bool atEnd = (unsigned long)pos.offset() >= textNode->length();
3374 if (pos.offset() > 0 && !atEnd) {
3375 splitTextNode(textNode, pos.offset());
3376 pos = Position(startNode, 0);
3379 newStartNode = startNode->traverseNextNode();
3380 ASSERT(newStartNode);
3383 else if (pos.offset() > 0) {
3384 newStartNode = startNode->traverseNextNode();
3385 ASSERT(newStartNode);
3388 // If a new start node was determined, find a new top block quote.
3390 startNode = newStartNode;
3391 for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
3392 if (isMailBlockquote(n))
3395 if (!topBlockquote || !topBlockquote->parentNode())
3399 // Build up list of ancestors in between the start node and the top blockquote.
3400 if (startNode != topBlockquote) {
3401 for (NodeImpl *n = startNode->parentNode(); n && n != topBlockquote; n = n->parentNode())
3402 ancestors.prepend(n);
3405 // Insert a clone of the top blockquote after the break.
3406 NodeImpl *clonedBlockquote = topBlockquote->cloneNode(false);
3407 clonedBlockquote->ref();
3408 clonedNodes.append(clonedBlockquote);
3409 insertNodeAfter(clonedBlockquote, m_breakNode);
3411 // Make clones of ancestors in between the start node and the top blockquote.
3412 NodeImpl *parent = clonedBlockquote;
3413 for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
3414 NodeImpl *child = it.current()->cloneNode(false); // shallow clone
3416 clonedNodes.append(child);
3417 appendNode(child, parent);
3421 // Move the start node and the siblings of the start node.
3422 bool startIsBR = false;
3423 if (startNode != topBlockquote) {
3424 NodeImpl *n = startNode;
3425 startIsBR = n->id() == ID_BR;
3427 n = n->nextSibling();
3429 NodeImpl *next = n->nextSibling();
3431 appendNode(n, parent);