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 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 &matchNearestBlockquoteColorString()
147 static DOMString matchNearestBlockquoteColorString = "match";
148 return matchNearestBlockquoteColorString;
151 static void derefNodesInList(QPtrList<NodeImpl> &list)
153 for (QPtrListIterator<NodeImpl> it(list); it.current(); ++it)
154 it.current()->deref();
157 static int maxRangeOffset(NodeImpl *n)
159 if (DOM::offsetInCharacters(n->nodeType()))
160 return n->maxOffset();
162 if (n->isElementNode())
163 return n->childNodeCount();
168 static int maxDeepOffset(NodeImpl *n)
170 if (n->isAtomicNode())
171 return n->caretMaxOffset();
173 if (n->isElementNode())
174 return n->childNodeCount();
179 static void debugPosition(const char *prefix, const Position &pos)
184 LOG(Editing, "%s <null>", prefix);
186 LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
189 static void debugNode(const char *prefix, const NodeImpl *node)
194 LOG(Editing, "%s <null>", prefix);
196 LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node);
199 static bool isSpecialElement(NodeImpl *n)
201 if (!n->isHTMLElement())
204 if (n->id() == ID_A && n->isLink())
207 if (n->id() == ID_UL || n->id() == ID_OL || n->id() == ID_DL)
210 RenderObject *renderer = n->renderer();
212 if (renderer && (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE))
215 if (renderer && renderer->style()->isFloating())
218 if (renderer && renderer->style()->position() != STATIC)
224 // This version of the function is meant to be called on positions in a document fragment,
225 // so it does not check for a root editable element, it is assumed these nodes will be put
226 // somewhere editable in the future
227 static bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos)
229 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
231 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
232 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
234 if (isSpecialElement(n))
241 static bool isFirstVisiblePositionInSpecialElement(const Position& pos)
243 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
245 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
246 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
248 if (n->rootEditableElement() == NULL)
250 if (isSpecialElement(n))
257 static Position positionBeforeNode(NodeImpl *node)
259 return Position(node->parentNode(), node->nodeIndex());
262 static Position positionBeforeContainingSpecialElement(const Position& pos)
264 ASSERT(isFirstVisiblePositionInSpecialElement(pos));
266 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
268 NodeImpl *outermostSpecialElement = NULL;
270 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
271 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
273 if (n->rootEditableElement() == NULL)
275 if (isSpecialElement(n))
276 outermostSpecialElement = n;
279 ASSERT(outermostSpecialElement);
281 Position result = positionBeforeNode(outermostSpecialElement);
282 if (result.isNull() || !result.node()->rootEditableElement())
288 static bool isLastVisiblePositionInSpecialElement(const Position& pos)
290 // make sure to get a range-compliant version of the position
291 Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
293 VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
295 for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
296 if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
298 if (n->rootEditableElement() == NULL)
300 if (isSpecialElement(n))
307 static Position positionAfterNode(NodeImpl *node)
309 return Position(node->parentNode(), node->nodeIndex() + 1);
312 static Position positionAfterContainingSpecialElement(const Position& pos)
314 ASSERT(isLastVisiblePositionInSpecialElement(pos));
316 // make sure to get a range-compliant version of the position
317 Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
319 VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
321 NodeImpl *outermostSpecialElement = NULL;
323 for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
324 if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
326 if (n->rootEditableElement() == NULL)
328 if (isSpecialElement(n))
329 outermostSpecialElement = n;
332 ASSERT(outermostSpecialElement);
334 Position result = positionAfterNode(outermostSpecialElement);
335 if (result.isNull() || !result.node()->rootEditableElement())
341 static Position positionOutsideContainingSpecialElement(const Position &pos)
343 if (isFirstVisiblePositionInSpecialElement(pos)) {
344 return positionBeforeContainingSpecialElement(pos);
345 } else if (isLastVisiblePositionInSpecialElement(pos)) {
346 return positionAfterContainingSpecialElement(pos);
352 static Position positionBeforePossibleContainingSpecialElement(const Position &pos)
354 if (isFirstVisiblePositionInSpecialElement(pos)) {
355 return positionBeforeContainingSpecialElement(pos);
361 static Position positionAfterPossibleContainingSpecialElement(const Position &pos)
363 if (isLastVisiblePositionInSpecialElement(pos)) {
364 return positionAfterContainingSpecialElement(pos);
370 //------------------------------------------------------------------------------------------
371 // DeleteFromTextNodeCommand
373 DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(DocumentImpl *document, TextImpl *node, long offset, long count)
374 : EditCommand(document), m_node(node), m_offset(offset), m_count(count)
377 ASSERT(m_offset >= 0);
378 ASSERT(m_offset < (long)m_node->length());
379 ASSERT(m_count >= 0);
384 DeleteFromTextNodeCommand::~DeleteFromTextNodeCommand()
390 void DeleteFromTextNodeCommand::doApply()
394 int exceptionCode = 0;
395 m_text = m_node->substringData(m_offset, m_count, exceptionCode);
396 ASSERT(exceptionCode == 0);
398 m_node->deleteData(m_offset, m_count, exceptionCode);
399 ASSERT(exceptionCode == 0);
402 void DeleteFromTextNodeCommand::doUnapply()
405 ASSERT(!m_text.isEmpty());
407 int exceptionCode = 0;
408 m_node->insertData(m_offset, m_text, exceptionCode);
409 ASSERT(exceptionCode == 0);
412 //------------------------------------------------------------------------------------------
413 // DeleteSelectionCommand
415 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, bool smartDelete, bool mergeBlocksAfterDelete)
416 : CompositeEditCommand(document),
417 m_hasSelectionToDelete(false),
418 m_smartDelete(smartDelete),
419 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
427 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
428 : CompositeEditCommand(document),
429 m_hasSelectionToDelete(true),
430 m_smartDelete(smartDelete),
431 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
432 m_selectionToDelete(selection),
440 void DeleteSelectionCommand::initializePositionData()
443 // Handle setting some basic positions
445 Position start = m_selectionToDelete.start();
446 start = positionOutsideContainingSpecialElement(start);
447 Position end = m_selectionToDelete.end();
448 end = positionOutsideContainingSpecialElement(end);
450 m_upstreamStart = positionBeforePossibleContainingSpecialElement(start.upstream());
451 m_downstreamStart = positionBeforePossibleContainingSpecialElement(start.downstream());
452 m_upstreamEnd = positionAfterPossibleContainingSpecialElement(end.upstream());
453 m_downstreamEnd = positionAfterPossibleContainingSpecialElement(end.downstream());
456 // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
458 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity());
459 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
463 // skip smart delete if the selection to delete already starts or ends with whitespace
464 Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()).deepEquivalent();
465 bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
466 if (!skipSmartDelete)
467 skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
469 // extend selection upstream if there is whitespace there
470 bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity(), true).isNotNull();
471 if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
472 VisiblePosition visiblePos = VisiblePosition(start, m_selectionToDelete.startAffinity()).previous();
473 pos = visiblePos.deepEquivalent();
474 // Expand out one character upstream for smart delete and recalculate
475 // positions based on this change.
476 m_upstreamStart = pos.upstream();
477 m_downstreamStart = pos.downstream();
478 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
481 // trailing whitespace is only considered for smart delete if there is no leading
482 // whitespace, as in the case where you double-click the first word of a paragraph.
483 if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
484 // Expand out one character downstream for smart delete and recalculate
485 // positions based on this change.
486 pos = VisiblePosition(end, m_selectionToDelete.endAffinity()).next().deepEquivalent();
487 m_upstreamEnd = pos.upstream();
488 m_downstreamEnd = pos.downstream();
489 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
493 m_trailingWhitespaceValid = true;
496 // Handle setting start and end blocks and the start node.
498 m_startBlock = m_downstreamStart.node()->enclosingBlockFlowElement();
500 m_endBlock = m_upstreamEnd.node()->enclosingBlockFlowElement();
502 m_startNode = m_upstreamStart.node();
506 // Handle detecting if the line containing the selection end is itself fully selected.
507 // This is one of the tests that determines if block merging of content needs to be done.
509 VisiblePosition visibleEnd(end, m_selectionToDelete.endAffinity());
510 if (isStartOfParagraph(visibleEnd) || isEndOfParagraph(visibleEnd)) {
511 Position previousLineStart = previousLinePosition(visibleEnd, 0).deepEquivalent();
512 if (previousLineStart.isNull() || RangeImpl::compareBoundaryPoints(previousLineStart, m_downstreamStart) >= 0)
513 m_mergeBlocksAfterDelete = false;
516 debugPosition("m_upstreamStart ", m_upstreamStart);
517 debugPosition("m_downstreamStart ", m_downstreamStart);
518 debugPosition("m_upstreamEnd ", m_upstreamEnd);
519 debugPosition("m_downstreamEnd ", m_downstreamEnd);
520 debugPosition("m_leadingWhitespace ", m_leadingWhitespace);
521 debugPosition("m_trailingWhitespace ", m_trailingWhitespace);
522 debugNode( "m_startBlock ", m_startBlock);
523 debugNode( "m_endBlock ", m_endBlock);
524 debugNode( "m_startNode ", m_startNode);
527 void DeleteSelectionCommand::insertPlaceholderForAncestorBlockContent()
529 // This code makes sure a line does not disappear when deleting in this case:
530 // <p>foo</p>bar<p>baz</p>
531 // Select "bar" and hit delete. If nothing is done, the line containing bar will disappear.
532 // It needs to be held open by inserting a placeholder.
534 // <rdar://problem/3928305> selecting an entire line and typing over causes new inserted text at top of document
536 // The checks below detect the case where the selection contains content in an ancestor block
537 // surrounded by child blocks.
539 VisiblePosition visibleStart(m_upstreamStart, VP_DEFAULT_AFFINITY);
540 VisiblePosition beforeStart = visibleStart.previous();
541 NodeImpl *startBlock = enclosingBlockFlowElement(visibleStart);
542 NodeImpl *beforeStartBlock = enclosingBlockFlowElement(beforeStart);
544 if (!beforeStart.isNull() &&
545 !inSameBlock(visibleStart, beforeStart) &&
546 beforeStartBlock->isAncestor(startBlock) &&
547 startBlock != m_upstreamStart.node()) {
549 VisiblePosition visibleEnd(m_downstreamEnd, VP_DEFAULT_AFFINITY);
550 VisiblePosition afterEnd = visibleEnd.next();
552 if ((!afterEnd.isNull() && !inSameBlock(afterEnd, visibleEnd) && !inSameBlock(afterEnd, visibleStart)) ||
553 (m_downstreamEnd == m_selectionToDelete.end() && isEndOfParagraph(visibleEnd))) {
554 NodeImpl *block = createDefaultParagraphElement(document());
555 insertNodeBefore(block, m_upstreamStart.node());
556 addBlockPlaceholderIfNeeded(block);
557 m_endingPosition = Position(block, 0);
562 void DeleteSelectionCommand::saveTypingStyleState()
564 // Figure out the typing style in effect before the delete is done.
565 // FIXME: Improve typing style.
566 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
567 CSSComputedStyleDeclarationImpl *computedStyle = m_selectionToDelete.start().computedStyle();
568 computedStyle->ref();
569 m_typingStyle = computedStyle->copyInheritableProperties();
570 m_typingStyle->ref();
571 computedStyle->deref();
574 bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
576 // Check for special-case where the selection contains only a BR on a line by itself after another BR.
577 bool upstreamStartIsBR = m_startNode->id() == ID_BR;
578 bool downstreamStartIsBR = m_downstreamStart.node()->id() == ID_BR;
579 bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
580 if (isBROnLineByItself) {
581 removeNode(m_downstreamStart.node());
582 m_endingPosition = m_upstreamStart;
583 m_mergeBlocksAfterDelete = false;
587 // Not a special-case delete per se, but we can detect that the merging of content between blocks
588 // should not be done.
589 if (upstreamStartIsBR && downstreamStartIsBR)
590 m_mergeBlocksAfterDelete = false;
595 void DeleteSelectionCommand::setStartNode(NodeImpl *node)
597 NodeImpl *old = m_startNode;
605 void DeleteSelectionCommand::handleGeneralDelete()
607 int startOffset = m_upstreamStart.offset();
608 VisiblePosition visibleEnd = VisiblePosition(m_downstreamEnd, m_selectionToDelete.endAffinity());
609 bool endAtEndOfBlock = isEndOfBlock(visibleEnd);
611 // Handle some special cases where the selection begins and ends on specific visible units.
612 // Sometimes a node that is actually selected needs to be retained in order to maintain
613 // user expectations for the delete operation. Here is an example:
614 // 1. Open a new Blot or Mail document
615 // 2. hit Return ten times or so
616 // 3. Type a letter (do not hit Return after it)
617 // 4. Type shift-up-arrow to select the line containing the letter and the previous blank line
619 // You expect the insertion point to wind up at the start of the line where your selection began.
620 // Because of the nature of HTML, the editing code needs to perform a special check to get
621 // this behavior. So:
622 // If the entire start block is selected, and the selection does not extend to the end of the
623 // end of a block other than the block containing the selection start, then do not delete the
624 // start block, otherwise delete the start block.
625 // A similar case is provided to cover selections starting in BR elements.
626 if (startOffset == 1 && m_startNode && m_startNode->id() == ID_BR) {
627 setStartNode(m_startNode->traverseNextNode());
630 if (m_startBlock != m_endBlock && startOffset == 0 && m_startNode && m_startNode->id() == ID_BR && endAtEndOfBlock) {
631 // Don't delete the BR element
632 setStartNode(m_startNode->traverseNextNode());
634 else if (m_startBlock != m_endBlock && isStartOfBlock(VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()))) {
635 if (!m_startBlock->isAncestor(m_endBlock) && !isStartOfBlock(visibleEnd) && endAtEndOfBlock) {
636 // Delete all the children of the block, but not the block itself.
637 setStartNode(m_startBlock->firstChild());
641 else if (startOffset >= m_startNode->caretMaxOffset() &&
642 (m_startNode->isAtomicNode() || startOffset == 0)) {
643 // Move the start node to the next node in the tree since the startOffset is equal to
644 // or beyond the start node's caretMaxOffset This means there is nothing visible to delete.
645 // But don't do this if the node is not atomic - we don't want to move into the first child.
647 // Also, before moving on, delete any insignificant text that may be present in a text node.
648 if (m_startNode->isTextNode()) {
649 // Delete any insignificant text from this node.
650 TextImpl *text = static_cast<TextImpl *>(m_startNode);
651 if (text->length() > (unsigned)m_startNode->caretMaxOffset())
652 deleteTextFromNode(text, m_startNode->caretMaxOffset(), text->length() - m_startNode->caretMaxOffset());
655 // shift the start node to the next
656 setStartNode(m_startNode->traverseNextNode());
660 // Done adjusting the start. See if we're all done.
664 if (m_startNode == m_downstreamEnd.node()) {
665 // The selection to delete is all in one node.
666 if (!m_startNode->renderer() ||
667 (startOffset == 0 && m_downstreamEnd.offset() >= maxDeepOffset(m_startNode))) {
669 removeFullySelectedNode(m_startNode);
670 } else if (m_downstreamEnd.offset() - startOffset > 0) {
671 if (m_startNode->isTextNode()) {
672 // in a text node that needs to be trimmed
673 TextImpl *text = static_cast<TextImpl *>(m_startNode);
674 deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset);
675 m_trailingWhitespaceValid = false;
677 removeChildrenInRange(m_startNode, startOffset, m_downstreamEnd.offset());
678 m_endingPosition = m_upstreamStart;
683 // The selection to delete spans more than one node.
684 NodeImpl *node = m_startNode;
686 if (startOffset > 0) {
687 if (m_startNode->isTextNode()) {
688 // in a text node that needs to be trimmed
689 TextImpl *text = static_cast<TextImpl *>(node);
690 deleteTextFromNode(text, startOffset, text->length() - startOffset);
691 node = node->traverseNextNode();
693 node = m_startNode->childNode(startOffset);
697 // handle deleting all nodes that are completely selected
698 while (node && node != m_downstreamEnd.node()) {
699 if (RangeImpl::compareBoundaryPoints(Position(node, 0), m_downstreamEnd) >= 0) {
700 // traverseNextSibling just blew past the end position, so stop deleting
702 } else if (!m_downstreamEnd.node()->isAncestor(node)) {
703 NodeImpl *nextNode = node->traverseNextSibling();
704 // if we just removed a node from the end container, update end position so the
705 // check above will work
706 if (node->parentNode() == m_downstreamEnd.node()) {
707 ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.offset());
708 m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.offset() - 1);
710 removeFullySelectedNode(node);
713 NodeImpl *n = node->lastChild();
714 while (n && n->lastChild())
716 if (n == m_downstreamEnd.node() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMaxOffset()) {
717 removeFullySelectedNode(node);
718 m_trailingWhitespaceValid = false;
722 node = node->traverseNextNode();
728 if (m_downstreamEnd.node() != m_startNode && !m_upstreamStart.node()->isAncestor(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMinOffset()) {
729 if (m_downstreamEnd.offset() >= maxDeepOffset(m_downstreamEnd.node())) {
730 // need to delete whole node
731 // we can get here if this is the last node in the block
732 // remove an ancestor of m_downstreamEnd.node(), and thus m_downstreamEnd.node() itself
733 if (!m_upstreamStart.node()->inDocument() ||
734 m_upstreamStart.node() == m_downstreamEnd.node() ||
735 m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
736 m_upstreamStart = Position(m_downstreamEnd.node()->parentNode(), m_downstreamEnd.node()->nodeIndex());
739 removeFullySelectedNode(m_downstreamEnd.node());
740 m_trailingWhitespaceValid = false;
742 if (m_downstreamEnd.node()->isTextNode()) {
743 // in a text node that needs to be trimmed
744 TextImpl *text = static_cast<TextImpl *>(m_downstreamEnd.node());
745 if (m_downstreamEnd.offset() > 0) {
746 deleteTextFromNode(text, 0, m_downstreamEnd.offset());
747 m_downstreamEnd = Position(text, 0);
748 m_trailingWhitespaceValid = false;
752 if (m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
753 NodeImpl *n = m_upstreamStart.node();
754 while (n && n->parentNode() != m_downstreamEnd.node())
757 offset = n->nodeIndex() + 1;
759 removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.offset());
760 m_downstreamEnd = Position(m_downstreamEnd.node(), offset);
767 void DeleteSelectionCommand::fixupWhitespace()
769 document()->updateLayout();
770 if (m_leadingWhitespace.isNotNull() && (m_trailingWhitespace.isNotNull() || !m_leadingWhitespace.isRenderedCharacter())) {
771 LOG(Editing, "replace leading");
772 TextImpl *textNode = static_cast<TextImpl *>(m_leadingWhitespace.node());
773 replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
775 else if (m_trailingWhitespace.isNotNull()) {
776 if (m_trailingWhitespaceValid) {
777 if (!m_trailingWhitespace.isRenderedCharacter()) {
778 LOG(Editing, "replace trailing [valid]");
779 TextImpl *textNode = static_cast<TextImpl *>(m_trailingWhitespace.node());
780 replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
784 Position pos = m_endingPosition.downstream();
785 pos = Position(pos.node(), pos.offset() - 1);
786 if (nextCharacterIsCollapsibleWhitespace(pos) && !pos.isRenderedCharacter()) {
787 LOG(Editing, "replace trailing [invalid]");
788 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
789 replaceTextInNode(textNode, pos.offset(), 1, nonBreakingSpaceString());
790 // need to adjust ending position since the trailing position is not valid.
791 m_endingPosition = pos;
797 // This function moves nodes in the block containing startNode to dstBlock, starting
798 // from startNode and proceeding to the end of the paragraph. Nodes in the block containing
799 // startNode that appear in document order before startNode are not moved.
800 // This function is an important helper for deleting selections that cross paragraph
802 void DeleteSelectionCommand::moveNodesAfterNode()
804 if (!m_mergeBlocksAfterDelete)
807 if (m_endBlock == m_startBlock)
810 NodeImpl *startNode = m_downstreamEnd.node();
811 NodeImpl *dstNode = m_upstreamStart.node();
813 if (!startNode->inDocument() || !dstNode->inDocument())
816 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
817 if (isTableStructureNode(startBlock) || isListStructureNode(startBlock))
818 // Do not move content between parts of a table or list.
821 // Now that we are about to add content, check to see if a placeholder element
823 removeBlockPlaceholder(startBlock);
825 // Move the subtree containing node
826 NodeImpl *node = startNode->enclosingInlineElement();
828 // Insert after the subtree containing destNode
829 NodeImpl *refNode = dstNode->enclosingInlineElement();
831 // Nothing to do if start is already at the beginning of dstBlock
832 NodeImpl *dstBlock = refNode->enclosingBlockFlowElement();
833 if (startBlock == dstBlock->firstChild())
837 NodeImpl *rootNode = refNode->rootEditableElement();
838 while (node && node->isAncestor(startBlock)) {
839 NodeImpl *moveNode = node;
840 node = node->nextSibling();
841 removeNode(moveNode);
842 if (moveNode->id() == ID_BR && !moveNode->renderer()) {
843 // Just remove this node, and don't put it back.
844 // If the BR was not rendered (since it was at the end of a block, for instance),
845 // putting it back in the document might make it appear, and that is not desirable.
848 if (refNode == rootNode)
849 insertNodeAt(moveNode, refNode, 0);
851 insertNodeAfter(moveNode, refNode);
853 if (moveNode->id() == ID_BR)
857 // If the startBlock no longer has any kids, we may need to deal with adding a BR
858 // to make the layout come out right. Consider this document:
864 // Placing the insertion before before the 'T' of 'Two' and hitting delete will
865 // move the contents of the div to the block containing 'One' and delete the div.
866 // This will have the side effect of moving 'Three' on to the same line as 'One'
867 // and 'Two'. This is undesirable. We fix this up by adding a BR before the 'Three'.
868 // This may not be ideal, but it is better than nothing.
869 document()->updateLayout();
870 if (!startBlock->renderer() || !startBlock->renderer()->firstChild()) {
871 removeNode(startBlock);
872 document()->updateLayout();
873 if (refNode->renderer() && refNode->renderer()->inlineBox() && refNode->renderer()->inlineBox()->nextOnLineExists()) {
874 insertNodeAfter(createBreakElement(document()), refNode);
879 void DeleteSelectionCommand::calculateEndingPosition()
881 if (m_endingPosition.isNotNull() && m_endingPosition.node()->inDocument())
884 m_endingPosition = m_upstreamStart;
885 if (m_endingPosition.node()->inDocument())
888 m_endingPosition = m_downstreamEnd;
889 if (m_endingPosition.node()->inDocument())
892 m_endingPosition = Position(m_startBlock, 0);
893 if (m_endingPosition.node()->inDocument())
896 m_endingPosition = Position(m_endBlock, 0);
897 if (m_endingPosition.node()->inDocument())
900 m_endingPosition = Position(document()->documentElement(), 0);
903 void DeleteSelectionCommand::calculateTypingStyleAfterDelete(NodeImpl *insertedPlaceholder)
905 // Compute the difference between the style before the delete and the style now
906 // after the delete has been done. Set this style on the part, so other editing
907 // commands being composed with this one will work, and also cache it on the command,
908 // so the KHTMLPart::appliedEditing can set it after the whole composite command
910 // FIXME: Improve typing style.
911 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
912 CSSComputedStyleDeclarationImpl endingStyle(m_endingPosition.node());
913 endingStyle.diff(m_typingStyle);
914 if (!m_typingStyle->length()) {
915 m_typingStyle->deref();
918 if (insertedPlaceholder && m_typingStyle) {
919 // Apply style to the placeholder. This makes sure that the single line in the
920 // paragraph has the right height, and that the paragraph takes on the style
921 // of the preceding line and retains it even if you click away, click back, and
922 // then start typing. In this case, the typing style is applied right now, and
923 // is not retained until the next typing action.
925 // FIXME: is this even right? I don't think post-deletion typing style is supposed
926 // to be saved across clicking away and clicking back, it certainly isn't in TextEdit
928 Position pastPlaceholder(insertedPlaceholder, 1);
930 setEndingSelection(Selection(m_endingPosition, m_selectionToDelete.endAffinity(), pastPlaceholder, DOWNSTREAM));
932 applyStyle(m_typingStyle, EditActionUnspecified);
934 m_typingStyle->deref();
937 // Set m_typingStyle as the typing style.
938 // It's perfectly OK for m_typingStyle to be null.
939 document()->part()->setTypingStyle(m_typingStyle);
940 setTypingStyle(m_typingStyle);
943 void DeleteSelectionCommand::clearTransientState()
945 m_selectionToDelete.clear();
946 m_upstreamStart.clear();
947 m_downstreamStart.clear();
948 m_upstreamEnd.clear();
949 m_downstreamEnd.clear();
950 m_endingPosition.clear();
951 m_leadingWhitespace.clear();
952 m_trailingWhitespace.clear();
955 m_startBlock->deref();
963 m_startNode->deref();
967 m_typingStyle->deref();
972 void DeleteSelectionCommand::doApply()
974 // If selection has not been set to a custom selection when the command was created,
975 // use the current ending selection.
976 if (!m_hasSelectionToDelete)
977 m_selectionToDelete = endingSelection();
979 if (!m_selectionToDelete.isRange())
982 // save this to later make the selection with
983 EAffinity affinity = m_selectionToDelete.startAffinity();
986 initializePositionData();
988 if (!m_startBlock || !m_endBlock) {
989 // Can't figure out what blocks we're in. This can happen if
990 // the document structure is not what we are expecting, like if
991 // the document has no body element, or if the editable block
992 // has been changed to display: inline. Some day it might
993 // be nice to be able to deal with this, but for now, bail.
994 clearTransientState();
998 // if all we are deleting is complete paragraph(s), we need to make
999 // sure a blank paragraph remains when we are done
1000 bool forceBlankParagraph = isStartOfParagraph(VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY)) &&
1001 isEndOfParagraph(VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY));
1003 // Delete any text that may hinder our ability to fixup whitespace after the detele
1004 deleteInsignificantTextDownstream(m_trailingWhitespace);
1006 saveTypingStyleState();
1007 insertPlaceholderForAncestorBlockContent();
1009 if (!handleSpecialCaseBRDelete())
1010 handleGeneralDelete();
1012 // Do block merge if start and end of selection are in different blocks.
1013 moveNodesAfterNode();
1015 calculateEndingPosition();
1018 // if the m_endingPosition is already a blank paragraph, there is
1019 // no need to force a new one
1020 if (forceBlankParagraph &&
1021 isStartOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY)) &&
1022 isEndOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY))) {
1023 forceBlankParagraph = false;
1026 NodeImpl *addedPlaceholder = forceBlankParagraph ? insertBlockPlaceholder(m_endingPosition) :
1027 addBlockPlaceholderIfNeeded(m_endingPosition.node());
1029 calculateTypingStyleAfterDelete(addedPlaceholder);
1030 debugPosition("endingPosition ", m_endingPosition);
1031 setEndingSelection(Selection(m_endingPosition, affinity));
1032 clearTransientState();
1033 rebalanceWhitespace();
1036 EditAction DeleteSelectionCommand::editingAction() const
1038 // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
1039 // but in that case there's a TypingCommand that supplies the editingAction(), so
1040 // the Undo menu correctly shows "Undo Typing"
1041 return EditActionCut;
1044 bool DeleteSelectionCommand::preservesTypingStyle() const
1049 //------------------------------------------------------------------------------------------
1050 // InsertIntoTextNode
1052 InsertIntoTextNode::InsertIntoTextNode(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
1053 : EditCommand(document), m_node(node), m_offset(offset)
1056 ASSERT(m_offset >= 0);
1057 ASSERT(!text.isEmpty());
1060 m_text = text.copy(); // make a copy to ensure that the string never changes
1063 InsertIntoTextNode::~InsertIntoTextNode()
1069 void InsertIntoTextNode::doApply()
1072 ASSERT(m_offset >= 0);
1073 ASSERT(!m_text.isEmpty());
1075 int exceptionCode = 0;
1076 m_node->insertData(m_offset, m_text, exceptionCode);
1077 ASSERT(exceptionCode == 0);
1080 void InsertIntoTextNode::doUnapply()
1083 ASSERT(m_offset >= 0);
1084 ASSERT(!m_text.isEmpty());
1086 int exceptionCode = 0;
1087 m_node->deleteData(m_offset, m_text.length(), exceptionCode);
1088 ASSERT(exceptionCode == 0);
1091 //------------------------------------------------------------------------------------------
1092 // InsertLineBreakCommand
1094 InsertLineBreakCommand::InsertLineBreakCommand(DocumentImpl *document)
1095 : CompositeEditCommand(document)
1099 bool InsertLineBreakCommand::preservesTypingStyle() const
1104 void InsertLineBreakCommand::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
1106 // Insert the BR after the caret position. In the case the
1107 // position is a block, do an append. We don't want to insert
1108 // the BR *after* the block.
1109 Position upstream(pos.upstream());
1110 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
1111 if (cb == pos.node())
1112 appendNode(node, cb);
1114 insertNodeAfter(node, pos.node());
1117 void InsertLineBreakCommand::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
1119 // Insert the BR after the caret position. In the case the
1120 // position is a block, do an append. We don't want to insert
1121 // the BR *before* the block.
1122 Position upstream(pos.upstream());
1123 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
1124 if (cb == pos.node())
1125 appendNode(node, cb);
1127 insertNodeBefore(node, pos.node());
1130 void InsertLineBreakCommand::doApply()
1133 Selection selection = endingSelection();
1135 ElementImpl *breakNode = createBreakElement(document());
1136 NodeImpl *nodeToInsert = breakNode;
1138 Position pos(selection.start().upstream());
1140 pos = positionOutsideContainingSpecialElement(pos);
1142 bool atStart = pos.offset() <= pos.node()->caretMinOffset();
1143 bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
1144 bool atEndOfBlock = isEndOfBlock(VisiblePosition(pos, selection.startAffinity()));
1147 LOG(Editing, "input newline case 1");
1148 // Check for a trailing BR. If there isn't one, we'll need to insert an "extra" one.
1149 // This makes the "real" BR we want to insert appear in the rendering without any
1150 // significant side effects (and no real worries either since you can't arrow past
1152 if (pos.node()->id() == ID_BR && pos.offset() == 0) {
1153 // Already placed in a trailing BR. Insert "real" BR before it and leave the selection alone.
1154 insertNodeBefore(nodeToInsert, pos.node());
1157 NodeImpl *next = pos.node()->traverseNextNode();
1158 bool hasTrailingBR = next && next->id() == ID_BR && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
1159 insertNodeAfterPosition(nodeToInsert, pos);
1160 if (hasTrailingBR) {
1161 setEndingSelection(Selection(Position(next, 0), DOWNSTREAM));
1163 else if (!document()->inStrictMode()) {
1164 // Insert an "extra" BR at the end of the block.
1165 ElementImpl *extraBreakNode = createBreakElement(document());
1166 insertNodeAfter(extraBreakNode, nodeToInsert);
1167 setEndingSelection(Position(extraBreakNode, 0), DOWNSTREAM);
1172 LOG(Editing, "input newline case 2");
1173 // Insert node before downstream position, and place caret there as well.
1174 Position endingPosition = pos.downstream();
1175 insertNodeBeforePosition(nodeToInsert, endingPosition);
1176 setEndingSelection(endingPosition, DOWNSTREAM);
1179 LOG(Editing, "input newline case 3");
1180 // Insert BR after this node. Place caret in the position that is downstream
1181 // of the current position, reckoned before inserting the BR in between.
1182 Position endingPosition = pos.downstream();
1183 insertNodeAfterPosition(nodeToInsert, pos);
1184 setEndingSelection(endingPosition, DOWNSTREAM);
1187 // Split a text node
1188 LOG(Editing, "input newline case 4");
1189 ASSERT(pos.node()->isTextNode());
1192 int exceptionCode = 0;
1193 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1194 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
1195 deleteTextFromNode(textNode, 0, pos.offset());
1196 insertNodeBefore(textBeforeNode, textNode);
1197 insertNodeBefore(nodeToInsert, textNode);
1198 Position endingPosition = Position(textNode, 0);
1200 // Handle whitespace that occurs after the split
1201 document()->updateLayout();
1202 if (!endingPosition.isRenderedCharacter()) {
1203 // Clear out all whitespace and insert one non-breaking space
1204 deleteInsignificantTextDownstream(endingPosition);
1205 insertTextIntoNode(textNode, 0, nonBreakingSpaceString());
1208 setEndingSelection(endingPosition, DOWNSTREAM);
1211 // Handle the case where there is a typing style.
1212 // FIXME: Improve typing style.
1213 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
1215 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
1217 if (typingStyle && typingStyle->length() > 0) {
1218 Selection selectionBeforeStyle = endingSelection();
1220 DOM::RangeImpl *rangeAroundNode = document()->createRange();
1222 rangeAroundNode->selectNode(nodeToInsert, exception);
1224 // affinity is not really important since this is a temp selection
1225 // just for calling applyStyle
1226 setEndingSelection(Selection(rangeAroundNode, khtml::SEL_DEFAULT_AFFINITY, khtml::SEL_DEFAULT_AFFINITY));
1227 applyStyle(typingStyle);
1229 setEndingSelection(selectionBeforeStyle);
1232 rebalanceWhitespace();
1235 //------------------------------------------------------------------------------------------
1236 // InsertNodeBeforeCommand
1238 InsertNodeBeforeCommand::InsertNodeBeforeCommand(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
1239 : EditCommand(document), m_insertChild(insertChild), m_refChild(refChild)
1241 ASSERT(m_insertChild);
1242 m_insertChild->ref();
1248 InsertNodeBeforeCommand::~InsertNodeBeforeCommand()
1250 ASSERT(m_insertChild);
1251 m_insertChild->deref();
1254 m_refChild->deref();
1257 void InsertNodeBeforeCommand::doApply()
1259 ASSERT(m_insertChild);
1261 ASSERT(m_refChild->parentNode());
1263 int exceptionCode = 0;
1264 m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
1265 ASSERT(exceptionCode == 0);
1268 void InsertNodeBeforeCommand::doUnapply()
1270 ASSERT(m_insertChild);
1272 ASSERT(m_refChild->parentNode());
1274 int exceptionCode = 0;
1275 m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
1276 ASSERT(exceptionCode == 0);
1279 //------------------------------------------------------------------------------------------
1280 // InsertParagraphSeparatorCommand
1282 InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(DocumentImpl *document)
1283 : CompositeEditCommand(document), m_style(0)
1287 InsertParagraphSeparatorCommand::~InsertParagraphSeparatorCommand()
1289 derefNodesInList(clonedNodes);
1294 bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
1299 ElementImpl *InsertParagraphSeparatorCommand::createParagraphElement()
1301 ElementImpl *element = createDefaultParagraphElement(document());
1303 clonedNodes.append(element);
1307 void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
1309 // It is only important to set a style to apply later if we're at the boundaries of
1310 // a paragraph. Otherwise, content that is moved as part of the work of the command
1311 // will lend their styles to the new paragraph without any extra work needed.
1312 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
1313 if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos))
1318 m_style = styleAtPosition(pos);
1322 void InsertParagraphSeparatorCommand::applyStyleAfterInsertion()
1324 // FIXME: Improve typing style.
1325 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
1329 CSSComputedStyleDeclarationImpl endingStyle(endingSelection().start().node());
1330 endingStyle.diff(m_style);
1331 if (m_style->length() > 0) {
1332 applyStyle(m_style);
1336 void InsertParagraphSeparatorCommand::doApply()
1338 bool splitText = false;
1339 Selection selection = endingSelection();
1340 if (selection.isNone())
1343 Position pos = selection.start();
1344 EAffinity affinity = selection.startAffinity();
1346 // Delete the current selection.
1347 if (selection.isRange()) {
1348 calculateStyleBeforeInsertion(pos);
1349 deleteSelection(false, false);
1350 pos = endingSelection().start();
1351 affinity = endingSelection().startAffinity();
1354 pos = positionOutsideContainingSpecialElement(pos);
1356 calculateStyleBeforeInsertion(pos);
1358 // Find the start block.
1359 NodeImpl *startNode = pos.node();
1360 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
1361 if (!startBlock || !startBlock->parentNode())
1364 VisiblePosition visiblePos(pos, affinity);
1365 bool isFirstInBlock = isStartOfBlock(visiblePos);
1366 bool isLastInBlock = isEndOfBlock(visiblePos);
1367 bool startBlockIsRoot = startBlock == startBlock->rootEditableElement();
1369 // This is the block that is going to be inserted.
1370 NodeImpl *blockToInsert = startBlockIsRoot ? createParagraphElement() : startBlock->cloneNode(false);
1372 //---------------------------------------------------------------------
1373 // Handle empty block case.
1374 if (isFirstInBlock && isLastInBlock) {
1375 LOG(Editing, "insert paragraph separator: empty block case");
1376 if (startBlockIsRoot) {
1377 NodeImpl *extraBlock = createParagraphElement();
1378 appendNode(extraBlock, startBlock);
1379 appendBlockPlaceholder(extraBlock);
1380 appendNode(blockToInsert, startBlock);
1383 insertNodeAfter(blockToInsert, startBlock);
1385 appendBlockPlaceholder(blockToInsert);
1386 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
1387 applyStyleAfterInsertion();
1391 //---------------------------------------------------------------------
1392 // Handle case when position is in the last visible position in its block.
1393 if (isLastInBlock) {
1394 LOG(Editing, "insert paragraph separator: last in block case");
1395 if (startBlockIsRoot)
1396 appendNode(blockToInsert, startBlock);
1398 insertNodeAfter(blockToInsert, startBlock);
1399 appendBlockPlaceholder(blockToInsert);
1400 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
1401 applyStyleAfterInsertion();
1405 //---------------------------------------------------------------------
1406 // Handle case when position is in the first visible position in its block.
1407 // and similar case where upstream position is in another block.
1408 bool prevInDifferentBlock = !inSameBlock(visiblePos, visiblePos.previous());
1410 if (prevInDifferentBlock || isFirstInBlock) {
1411 LOG(Editing, "insert paragraph separator: first in block case");
1412 pos = pos.downstream();
1413 pos = positionOutsideContainingSpecialElement(pos);
1416 if (isFirstInBlock && !startBlockIsRoot) {
1417 refNode = startBlock;
1418 } else if (pos.node() == startBlock && startBlockIsRoot) {
1419 ASSERT(startBlock->childNode(pos.offset())); // must be true or we'd be in the end of block case
1420 refNode = startBlock->childNode(pos.offset());
1422 refNode = pos.node();
1425 insertNodeBefore(blockToInsert, refNode);
1426 appendBlockPlaceholder(blockToInsert);
1427 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
1428 applyStyleAfterInsertion();
1429 setEndingSelection(pos, DOWNSTREAM);
1433 //---------------------------------------------------------------------
1434 // Handle the (more complicated) general case,
1436 LOG(Editing, "insert paragraph separator: general case");
1438 // Check if pos.node() is a <br>. If it is, and the document is in quirks mode,
1439 // then this <br> will collapse away when we add a block after it. Add an extra <br>.
1440 if (!document()->inStrictMode()) {
1441 Position upstreamPos = pos.upstream();
1442 if (upstreamPos.node()->id() == ID_BR)
1443 insertNodeAfter(createBreakElement(document()), upstreamPos.node());
1446 // Move downstream. Typing style code will take care of carrying along the
1447 // style of the upstream position.
1448 pos = pos.downstream();
1449 startNode = pos.node();
1451 // Build up list of ancestors in between the start node and the start block.
1452 if (startNode != startBlock) {
1453 for (NodeImpl *n = startNode->parentNode(); n && n != startBlock; n = n->parentNode())
1454 ancestors.prepend(n);
1457 // Make sure we do not cause a rendered space to become unrendered.
1458 // FIXME: We need the affinity for pos, but pos.downstream() does not give it
1459 Position leadingWhitespace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
1460 if (leadingWhitespace.isNotNull()) {
1461 TextImpl *textNode = static_cast<TextImpl *>(leadingWhitespace.node());
1462 replaceTextInNode(textNode, leadingWhitespace.offset(), 1, nonBreakingSpaceString());
1465 // Split at pos if in the middle of a text node.
1466 if (startNode->isTextNode()) {
1467 TextImpl *textNode = static_cast<TextImpl *>(startNode);
1468 bool atEnd = (unsigned long)pos.offset() >= textNode->length();
1469 if (pos.offset() > 0 && !atEnd) {
1470 splitTextNode(textNode, pos.offset());
1471 pos = Position(startNode, 0);
1476 // Put the added block in the tree.
1477 if (startBlockIsRoot) {
1478 appendNode(blockToInsert, startBlock);
1480 insertNodeAfter(blockToInsert, startBlock);
1483 // Make clones of ancestors in between the start node and the start block.
1484 NodeImpl *parent = blockToInsert;
1485 for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
1486 NodeImpl *child = it.current()->cloneNode(false); // shallow clone
1488 clonedNodes.append(child);
1489 appendNode(child, parent);
1493 // Insert a block placeholder if the next visible position is in a different paragraph,
1494 // because we know that there will be no content on the first line of the new block
1495 // before the first block child. So, we need the placeholder to "hold the first line open".
1496 VisiblePosition next = visiblePos.next();
1497 if (!next.isNull() && !inSameBlock(visiblePos, next))
1498 appendBlockPlaceholder(blockToInsert);
1500 // Move the start node and the siblings of the start node.
1501 if (startNode != startBlock) {
1502 NodeImpl *n = startNode;
1503 if (pos.offset() >= startNode->caretMaxOffset()) {
1504 n = startNode->nextSibling();
1506 while (n && n != blockToInsert) {
1507 NodeImpl *next = n->nextSibling();
1509 appendNode(n, parent);
1514 // Move everything after the start node.
1515 NodeImpl *leftParent = ancestors.last();
1516 while (leftParent && leftParent != startBlock) {
1517 parent = parent->parentNode();
1518 NodeImpl *n = leftParent->nextSibling();
1519 while (n && n != blockToInsert) {
1520 NodeImpl *next = n->nextSibling();
1522 appendNode(n, parent);
1525 leftParent = leftParent->parentNode();
1528 // Handle whitespace that occurs after the split
1530 document()->updateLayout();
1531 pos = Position(startNode, 0);
1532 if (!pos.isRenderedCharacter()) {
1533 // Clear out all whitespace and insert one non-breaking space
1534 ASSERT(startNode && startNode->isTextNode());
1535 deleteInsignificantTextDownstream(pos);
1536 insertTextIntoNode(static_cast<TextImpl *>(startNode), 0, nonBreakingSpaceString());
1540 setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
1541 rebalanceWhitespace();
1542 applyStyleAfterInsertion();
1545 //------------------------------------------------------------------------------------------
1546 // InsertParagraphSeparatorInQuotedContentCommand
1548 InsertParagraphSeparatorInQuotedContentCommand::InsertParagraphSeparatorInQuotedContentCommand(DocumentImpl *document)
1549 : CompositeEditCommand(document), m_breakNode(0)
1553 InsertParagraphSeparatorInQuotedContentCommand::~InsertParagraphSeparatorInQuotedContentCommand()
1555 derefNodesInList(clonedNodes);
1557 m_breakNode->deref();
1560 void InsertParagraphSeparatorInQuotedContentCommand::doApply()
1562 Selection selection = endingSelection();
1563 if (selection.isNone())
1566 // Delete the current selection.
1567 Position pos = selection.start();
1568 EAffinity affinity = selection.startAffinity();
1569 if (selection.isRange()) {
1570 deleteSelection(false, false);
1571 pos = endingSelection().start().upstream();
1572 affinity = endingSelection().startAffinity();
1575 // Find the top-most blockquote from the start.
1576 NodeImpl *startNode = pos.node();
1577 NodeImpl *topBlockquote = 0;
1578 for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
1579 if (isMailBlockquote(n))
1582 if (!topBlockquote || !topBlockquote->parentNode())
1585 // Insert a break after the top blockquote.
1586 m_breakNode = createBreakElement(document());
1588 insertNodeAfter(m_breakNode, topBlockquote);
1590 if (!isLastVisiblePositionInNode(VisiblePosition(pos, affinity), topBlockquote)) {
1592 NodeImpl *newStartNode = 0;
1593 // Split at pos if in the middle of a text node.
1594 if (startNode->isTextNode()) {
1595 TextImpl *textNode = static_cast<TextImpl *>(startNode);
1596 bool atEnd = (unsigned long)pos.offset() >= textNode->length();
1597 if (pos.offset() > 0 && !atEnd) {
1598 splitTextNode(textNode, pos.offset());
1599 pos = Position(startNode, 0);
1602 newStartNode = startNode->traverseNextNode();
1603 ASSERT(newStartNode);
1606 else if (pos.offset() > 0) {
1607 newStartNode = startNode->traverseNextNode();
1608 ASSERT(newStartNode);
1611 // If a new start node was determined, find a new top block quote.
1613 startNode = newStartNode;
1614 for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
1615 if (isMailBlockquote(n))
1618 if (!topBlockquote || !topBlockquote->parentNode())
1622 // Build up list of ancestors in between the start node and the top blockquote.
1623 if (startNode != topBlockquote) {
1624 for (NodeImpl *n = startNode->parentNode(); n && n != topBlockquote; n = n->parentNode())
1625 ancestors.prepend(n);
1628 // Insert a clone of the top blockquote after the break.
1629 NodeImpl *clonedBlockquote = topBlockquote->cloneNode(false);
1630 clonedBlockquote->ref();
1631 clonedNodes.append(clonedBlockquote);
1632 insertNodeAfter(clonedBlockquote, m_breakNode);
1634 // Make clones of ancestors in between the start node and the top blockquote.
1635 NodeImpl *parent = clonedBlockquote;
1636 for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
1637 NodeImpl *child = it.current()->cloneNode(false); // shallow clone
1639 clonedNodes.append(child);
1640 appendNode(child, parent);
1644 // Move the start node and the siblings of the start node.
1645 bool startIsBR = false;
1646 if (startNode != topBlockquote) {
1647 NodeImpl *n = startNode;
1648 startIsBR = n->id() == ID_BR;
1650 n = n->nextSibling();
1652 NodeImpl *next = n->nextSibling();
1654 appendNode(n, parent);
1659 // Move everything after the start node.
1660 NodeImpl *leftParent = ancestors.last();
1662 // Insert an extra new line when the start is at the beginning of a line.
1663 if (!newStartNode && !startIsBR) {
1665 leftParent = topBlockquote;
1666 ElementImpl *b = createBreakElement(document());
1668 clonedNodes.append(b);
1669 appendNode(b, leftParent);
1672 leftParent = ancestors.last();
1673 while (leftParent && leftParent != topBlockquote) {
1674 parent = parent->parentNode();
1675 NodeImpl *n = leftParent->nextSibling();
1677 NodeImpl *next = n->nextSibling();
1679 appendNode(n, parent);
1682 leftParent = leftParent->parentNode();
1685 // Make sure the cloned block quote renders.
1686 addBlockPlaceholderIfNeeded(clonedBlockquote);
1689 // Put the selection right before the break.
1690 setEndingSelection(Position(m_breakNode, 0), DOWNSTREAM);
1691 rebalanceWhitespace();
1694 //------------------------------------------------------------------------------------------
1695 // InsertTextCommand
1697 InsertTextCommand::InsertTextCommand(DocumentImpl *document)
1698 : CompositeEditCommand(document), m_charactersAdded(0)
1702 void InsertTextCommand::doApply()
1706 Position InsertTextCommand::prepareForTextInsertion(bool adjustDownstream)
1708 // Prepare for text input by looking at the current position.
1709 // It may be necessary to insert a text node to receive characters.
1710 Selection selection = endingSelection();
1711 ASSERT(selection.isCaret());
1713 Position pos = selection.start();
1714 if (adjustDownstream)
1715 pos = pos.downstream();
1717 pos = pos.upstream();
1719 Selection typingStyleRange;
1721 pos = positionOutsideContainingSpecialElement(pos);
1723 if (!pos.node()->isTextNode()) {
1724 NodeImpl *textNode = document()->createEditingTextNode("");
1725 NodeImpl *nodeToInsert = textNode;
1727 // Now insert the node in the right place
1728 if (pos.node()->rootEditableElement() != NULL) {
1729 LOG(Editing, "prepareForTextInsertion case 1");
1730 insertNodeAt(nodeToInsert, pos.node(), pos.offset());
1732 else if (pos.node()->caretMinOffset() == pos.offset()) {
1733 LOG(Editing, "prepareForTextInsertion case 2");
1734 insertNodeBefore(nodeToInsert, pos.node());
1736 else if (pos.node()->caretMaxOffset() == pos.offset()) {
1737 LOG(Editing, "prepareForTextInsertion case 3");
1738 insertNodeAfter(nodeToInsert, pos.node());
1741 ASSERT_NOT_REACHED();
1743 pos = Position(textNode, 0);
1749 void InsertTextCommand::input(const DOMString &text, bool selectInsertedText)
1751 assert(text.find('\n') == -1);
1753 Selection selection = endingSelection();
1754 bool adjustDownstream = isStartOfLine(VisiblePosition(selection.start().downstream(), DOWNSTREAM));
1756 // Delete the current selection, or collapse whitespace, as needed
1757 if (selection.isRange())
1760 // Delete any insignificant text that could get in the way of whitespace turning
1761 // out correctly after the insertion.
1762 selection = endingSelection();
1763 deleteInsignificantTextDownstream(selection.end().trailingWhitespacePosition(selection.endAffinity()));
1765 // Make sure the document is set up to receive text
1766 Position startPosition = prepareForTextInsertion(adjustDownstream);
1768 Position endPosition;
1770 TextImpl *textNode = static_cast<TextImpl *>(startPosition.node());
1771 long offset = startPosition.offset();
1773 // Now that we are about to add content, check to see if a placeholder element
1775 removeBlockPlaceholder(textNode->enclosingBlockFlowElement());
1777 // These are temporary implementations for inserting adjoining spaces
1778 // into a document. We are working on a CSS-related whitespace solution
1779 // that will replace this some day. We hope.
1781 // Treat a tab like a number of spaces. This seems to be the HTML editing convention,
1782 // although the number of spaces varies (we choose four spaces).
1783 // Note that there is no attempt to make this work like a real tab stop, it is merely
1784 // a set number of spaces. This also seems to be the HTML editing convention.
1785 for (int i = 0; i < spacesPerTab; i++) {
1786 insertSpace(textNode, offset);
1787 rebalanceWhitespace();
1788 document()->updateLayout();
1791 endPosition = Position(textNode, offset + spacesPerTab);
1793 m_charactersAdded += spacesPerTab;
1795 else if (text == " ") {
1796 insertSpace(textNode, offset);
1797 endPosition = Position(textNode, offset + 1);
1799 m_charactersAdded++;
1800 rebalanceWhitespace();
1803 const DOMString &existingText = textNode->data();
1804 if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isCollapsibleWhitespace(existingText[offset - 2])) {
1805 // DOM looks like this:
1806 // character nbsp caret
1807 // As we are about to insert a non-whitespace character at the caret
1808 // convert the nbsp to a regular space.
1809 // EDIT FIXME: This needs to be improved some day to convert back only
1810 // those nbsp's added by the editor to make rendering come out right.
1811 replaceTextInNode(textNode, offset - 1, 1, " ");
1813 insertTextIntoNode(textNode, offset, text);
1814 endPosition = Position(textNode, offset + text.length());
1816 m_charactersAdded += text.length();
1819 setEndingSelection(Selection(startPosition, DOWNSTREAM, endPosition, SEL_DEFAULT_AFFINITY));
1821 // Handle the case where there is a typing style.
1822 // FIXME: Improve typing style.
1823 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
1824 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
1825 if (typingStyle && typingStyle->length() > 0)
1826 applyStyle(typingStyle);
1828 if (!selectInsertedText)
1829 setEndingSelection(endingSelection().end(), endingSelection().endAffinity());
1832 void InsertTextCommand::insertSpace(TextImpl *textNode, unsigned long offset)
1836 DOMString text(textNode->data());
1838 // count up all spaces and newlines in front of the caret
1839 // delete all collapsed ones
1840 // this will work out OK since the offset we have been passed has been upstream-ized
1842 for (unsigned int i = offset; i < text.length(); i++) {
1843 if (isCollapsibleWhitespace(text[i]))
1849 // By checking the character at the downstream position, we can
1850 // check if there is a rendered WS at the caret
1851 Position pos(textNode, offset);
1852 Position downstream = pos.downstream();
1853 if (downstream.offset() < (long)text.length() && isCollapsibleWhitespace(text[downstream.offset()]))
1854 count--; // leave this WS in
1856 deleteTextFromNode(textNode, offset, count);
1859 if (offset > 0 && offset <= text.length() - 1 && !isCollapsibleWhitespace(text[offset]) && !isCollapsibleWhitespace(text[offset - 1])) {
1860 // insert a "regular" space
1861 insertTextIntoNode(textNode, offset, " ");
1865 if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
1866 // DOM looks like this:
1868 // insert a space between the two nbsps
1869 insertTextIntoNode(textNode, offset - 1, " ");
1874 insertTextIntoNode(textNode, offset, nonBreakingSpaceString());
1877 bool InsertTextCommand::isInsertTextCommand() const
1882 //------------------------------------------------------------------------------------------
1883 // JoinTextNodesCommand
1885 JoinTextNodesCommand::JoinTextNodesCommand(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
1886 : EditCommand(document), m_text1(text1), m_text2(text2)
1890 ASSERT(m_text1->nextSibling() == m_text2);
1891 ASSERT(m_text1->length() > 0);
1892 ASSERT(m_text2->length() > 0);
1898 JoinTextNodesCommand::~JoinTextNodesCommand()
1906 void JoinTextNodesCommand::doApply()
1910 ASSERT(m_text1->nextSibling() == m_text2);
1912 int exceptionCode = 0;
1913 m_text2->insertData(0, m_text1->data(), exceptionCode);
1914 ASSERT(exceptionCode == 0);
1916 m_text2->parentNode()->removeChild(m_text1, exceptionCode);
1917 ASSERT(exceptionCode == 0);
1919 m_offset = m_text1->length();
1922 void JoinTextNodesCommand::doUnapply()
1925 ASSERT(m_offset > 0);
1927 int exceptionCode = 0;
1929 m_text2->deleteData(0, m_offset, exceptionCode);
1930 ASSERT(exceptionCode == 0);
1932 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
1933 ASSERT(exceptionCode == 0);
1935 ASSERT(m_text2->previousSibling()->isTextNode());
1936 ASSERT(m_text2->previousSibling() == m_text1);
1939 //------------------------------------------------------------------------------------------
1940 // MoveSelectionCommand
1942 MoveSelectionCommand::MoveSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, Position &position, bool smartMove)
1943 : CompositeEditCommand(document), m_fragment(fragment), m_position(position), m_smartMove(smartMove)
1949 MoveSelectionCommand::~MoveSelectionCommand()
1952 m_fragment->deref();
1955 void MoveSelectionCommand::doApply()
1957 Selection selection = endingSelection();
1958 ASSERT(selection.isRange());
1960 Position pos = m_position;
1964 // Update the position otherwise it may become invalid after the selection is deleted.
1965 NodeImpl *positionNode = m_position.node();
1966 long positionOffset = m_position.offset();
1967 Position selectionEnd = selection.end();
1968 long selectionEndOffset = selectionEnd.offset();
1969 if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) {
1970 positionOffset -= selectionEndOffset;
1971 Position selectionStart = selection.start();
1972 if (selectionStart.node() == positionNode) {
1973 positionOffset += selectionStart.offset();
1975 pos = Position(positionNode, positionOffset);
1978 deleteSelection(m_smartMove);
1980 // If the node for the destination has been removed as a result of the deletion,
1981 // set the destination to the ending point after the deletion.
1982 // Fixes: <rdar://problem/3910425> REGRESSION (Mail): Crash in ReplaceSelectionCommand;
1983 // selection is empty, leading to null deref
1984 if (!pos.node()->inDocument())
1985 pos = endingSelection().start();
1987 setEndingSelection(pos, endingSelection().startAffinity());
1988 EditCommandPtr cmd(new ReplaceSelectionCommand(document(), m_fragment, true, m_smartMove));
1989 applyCommandToComposite(cmd);
1992 EditAction MoveSelectionCommand::editingAction() const
1994 return EditActionDrag;
1997 //------------------------------------------------------------------------------------------
1998 // RebalanceWhitespaceCommand
2000 RebalanceWhitespaceCommand::RebalanceWhitespaceCommand(DocumentImpl *document, const Position &pos)
2001 : EditCommand(document), m_position(pos), m_upstreamOffset(InvalidOffset), m_downstreamOffset(InvalidOffset)
2005 RebalanceWhitespaceCommand::~RebalanceWhitespaceCommand()
2009 void RebalanceWhitespaceCommand::doApply()
2011 static DOMString space(" ");
2013 if (m_position.isNull() || !m_position.node()->isTextNode())
2016 TextImpl *textNode = static_cast<TextImpl *>(m_position.node());
2017 DOMString text = textNode->data();
2018 if (text.length() == 0)
2021 // find upstream offset
2022 long upstream = m_position.offset();
2023 while (upstream > 0 && isCollapsibleWhitespace(text[upstream - 1]) || isNBSP(text[upstream - 1])) {
2025 m_upstreamOffset = upstream;
2028 // find downstream offset
2029 long downstream = m_position.offset();
2030 while ((unsigned)downstream < text.length() && isCollapsibleWhitespace(text[downstream]) || isNBSP(text[downstream])) {
2032 m_downstreamOffset = downstream;
2035 if (m_upstreamOffset == InvalidOffset && m_downstreamOffset == InvalidOffset)
2038 m_upstreamOffset = upstream;
2039 m_downstreamOffset = downstream;
2040 long length = m_downstreamOffset - m_upstreamOffset;
2042 m_beforeString = text.substring(m_upstreamOffset, length);
2044 // The following loop figures out a "rebalanced" whitespace string for any length
2045 // string, and takes into account the special cases that need to handled for the
2046 // start and end of strings (i.e. first and last character must be an nbsp.
2047 long i = m_upstreamOffset;
2048 while (i < m_downstreamOffset) {
2049 long add = (m_downstreamOffset - i) % 3;
2052 m_afterString += nonBreakingSpaceString();
2053 m_afterString += space;
2054 m_afterString += nonBreakingSpaceString();
2058 if (i == 0 || (unsigned)i + 1 == text.length()) // at start or end of string
2059 m_afterString += nonBreakingSpaceString();
2061 m_afterString += space;
2064 if ((unsigned)i + 2 == text.length()) {
2066 m_afterString += nonBreakingSpaceString();
2067 m_afterString += nonBreakingSpaceString();
2070 m_afterString += nonBreakingSpaceString();
2071 m_afterString += space;
2078 text.remove(m_upstreamOffset, length);
2079 text.insert(m_afterString, m_upstreamOffset);
2082 void RebalanceWhitespaceCommand::doUnapply()
2084 if (m_upstreamOffset == InvalidOffset && m_downstreamOffset == InvalidOffset)
2087 ASSERT(m_position.node()->isTextNode());
2088 TextImpl *textNode = static_cast<TextImpl *>(m_position.node());
2089 DOMString text = textNode->data();
2090 text.remove(m_upstreamOffset, m_afterString.length());
2091 text.insert(m_beforeString, m_upstreamOffset);
2094 bool RebalanceWhitespaceCommand::preservesTypingStyle() const
2099 //------------------------------------------------------------------------------------------
2100 // RemoveCSSPropertyCommand
2102 RemoveCSSPropertyCommand::RemoveCSSPropertyCommand(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property)
2103 : EditCommand(document), m_decl(decl->makeMutable()), m_property(property), m_important(false)
2109 RemoveCSSPropertyCommand::~RemoveCSSPropertyCommand()
2115 void RemoveCSSPropertyCommand::doApply()
2119 m_oldValue = m_decl->getPropertyValue(m_property);
2120 ASSERT(!m_oldValue.isNull());
2122 m_important = m_decl->getPropertyPriority(m_property);
2123 m_decl->removeProperty(m_property);
2126 void RemoveCSSPropertyCommand::doUnapply()
2129 ASSERT(!m_oldValue.isNull());
2131 m_decl->setProperty(m_property, m_oldValue, m_important);
2134 //------------------------------------------------------------------------------------------
2135 // RemoveNodeAttributeCommand
2137 RemoveNodeAttributeCommand::RemoveNodeAttributeCommand(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute)
2138 : EditCommand(document), m_element(element), m_attribute(attribute)
2144 RemoveNodeAttributeCommand::~RemoveNodeAttributeCommand()
2150 void RemoveNodeAttributeCommand::doApply()
2154 m_oldValue = m_element->getAttribute(m_attribute);
2155 ASSERT(!m_oldValue.isNull());
2157 int exceptionCode = 0;
2158 m_element->removeAttribute(m_attribute, exceptionCode);
2159 ASSERT(exceptionCode == 0);
2162 void RemoveNodeAttributeCommand::doUnapply()
2165 ASSERT(!m_oldValue.isNull());
2167 int exceptionCode = 0;
2168 m_element->setAttribute(m_attribute, m_oldValue.implementation(), exceptionCode);
2169 ASSERT(exceptionCode == 0);
2172 //------------------------------------------------------------------------------------------
2173 // RemoveNodeCommand
2175 RemoveNodeCommand::RemoveNodeCommand(DocumentImpl *document, NodeImpl *removeChild)
2176 : EditCommand(document), m_parent(0), m_removeChild(removeChild), m_refChild(0)
2178 ASSERT(m_removeChild);
2179 m_removeChild->ref();
2181 m_parent = m_removeChild->parentNode();
2185 m_refChild = m_removeChild->nextSibling();
2190 RemoveNodeCommand::~RemoveNodeCommand()
2195 ASSERT(m_removeChild);
2196 m_removeChild->deref();
2199 m_refChild->deref();
2202 void RemoveNodeCommand::doApply()
2205 ASSERT(m_removeChild);
2207 int exceptionCode = 0;
2208 m_parent->removeChild(m_removeChild, exceptionCode);
2209 ASSERT(exceptionCode == 0);
2212 void RemoveNodeCommand::doUnapply()
2215 ASSERT(m_removeChild);
2217 int exceptionCode = 0;
2218 m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
2219 ASSERT(exceptionCode == 0);
2222 //------------------------------------------------------------------------------------------
2223 // RemoveNodePreservingChildrenCommand
2225 RemoveNodePreservingChildrenCommand::RemoveNodePreservingChildrenCommand(DocumentImpl *document, NodeImpl *node)
2226 : CompositeEditCommand(document), m_node(node)
2232 RemoveNodePreservingChildrenCommand::~RemoveNodePreservingChildrenCommand()
2238 void RemoveNodePreservingChildrenCommand::doApply()
2240 while (NodeImpl* curr = node()->firstChild()) {
2242 insertNodeBefore(curr, node());
2247 //------------------------------------------------------------------------------------------
2248 // ReplaceSelectionCommand
2250 ReplacementFragment::ReplacementFragment(DocumentImpl *document, DocumentFragmentImpl *fragment, bool matchStyle)
2251 : m_document(document),
2252 m_fragment(fragment),
2253 m_matchStyle(matchStyle),
2254 m_hasInterchangeNewlineAtStart(false),
2255 m_hasInterchangeNewlineAtEnd(false),
2256 m_hasMoreThanOneBlock(false)
2262 m_type = EmptyFragment;
2269 NodeImpl *firstChild = m_fragment->firstChild();
2270 NodeImpl *lastChild = m_fragment->lastChild();
2273 m_type = EmptyFragment;
2277 if (firstChild == lastChild && firstChild->isTextNode()) {
2278 m_type = SingleTextNodeFragment;
2282 m_type = TreeFragment;
2284 NodeImpl *node = m_fragment->firstChild();
2285 NodeImpl *newlineAtStartNode = 0;
2286 NodeImpl *newlineAtEndNode = 0;
2288 NodeImpl *next = node->traverseNextNode();
2289 if (isInterchangeNewlineNode(node)) {
2290 if (next || node == m_fragment->firstChild()) {
2291 m_hasInterchangeNewlineAtStart = true;
2292 newlineAtStartNode = node;
2295 m_hasInterchangeNewlineAtEnd = true;
2296 newlineAtEndNode = node;
2299 else if (isInterchangeConvertedSpaceSpan(node)) {
2301 while ((n = node->firstChild())) {
2304 insertNodeBefore(n, node);
2309 next = n->traverseNextNode();
2314 if (newlineAtStartNode)
2315 removeNode(newlineAtStartNode);
2316 if (newlineAtEndNode)
2317 removeNode(newlineAtEndNode);
2319 NodeImpl *holder = insertFragmentForTestRendering();
2322 if (!m_matchStyle) {
2323 computeStylesUsingTestRendering(holder);
2325 removeUnrenderedNodesUsingTestRendering(holder);
2326 m_hasMoreThanOneBlock = countRenderedBlocks(holder) > 1;
2327 restoreTestRenderingNodesToFragment(holder);
2333 ReplacementFragment::~ReplacementFragment()
2336 m_document->deref();
2338 m_fragment->deref();
2341 NodeImpl *ReplacementFragment::firstChild() const
2343 return m_fragment->firstChild();
2346 NodeImpl *ReplacementFragment::lastChild() const
2348 return m_fragment->lastChild();
2351 NodeImpl *ReplacementFragment::mergeStartNode() const
2353 NodeImpl *node = m_fragment->firstChild();
2354 while (node && isProbablyBlock(node) && !isMailPasteAsQuotationNode(node))
2355 node = node->traverseNextNode();
2359 void ReplacementFragment::pruneEmptyNodes()
2364 NodeImpl *node = m_fragment->firstChild();
2366 if ((node->isTextNode() && static_cast<TextImpl *>(node)->length() == 0) ||
2367 (isProbablyBlock(node) && !isProbablyTableStructureNode(node) && node->childNodeCount() == 0)) {
2368 NodeImpl *next = node->traverseNextSibling();
2374 node = node->traverseNextNode();
2380 bool ReplacementFragment::isInterchangeNewlineNode(const NodeImpl *node)
2382 static DOMString interchangeNewlineClassString(AppleInterchangeNewline);
2383 return node && node->id() == ID_BR && static_cast<const ElementImpl *>(node)->getAttribute(ATTR_CLASS) == interchangeNewlineClassString;
2386 bool ReplacementFragment::isInterchangeConvertedSpaceSpan(const NodeImpl *node)
2388 static DOMString convertedSpaceSpanClassString(AppleConvertedSpace);
2389 return node->isHTMLElement() && static_cast<const HTMLElementImpl *>(node)->getAttribute(ATTR_CLASS) == convertedSpaceSpanClassString;
2392 NodeImpl *ReplacementFragment::enclosingBlock(NodeImpl *node) const
2394 while (node && !isProbablyBlock(node))
2395 node = node->parentNode();
2396 return node ? node : m_fragment;
2399 void ReplacementFragment::removeNodePreservingChildren(NodeImpl *node)
2404 while (NodeImpl *n = node->firstChild()) {
2407 insertNodeBefore(n, node);
2413 void ReplacementFragment::removeNode(NodeImpl *node)
2418 NodeImpl *parent = node->parentNode();
2422 int exceptionCode = 0;
2423 parent->removeChild(node, exceptionCode);
2424 ASSERT(exceptionCode == 0);
2427 void ReplacementFragment::insertNodeBefore(NodeImpl *node, NodeImpl *refNode)
2429 if (!node || !refNode)
2432 NodeImpl *parent = refNode->parentNode();
2436 int exceptionCode = 0;
2437 parent->insertBefore(node, refNode, exceptionCode);
2438 ASSERT(exceptionCode == 0);
2441 NodeImpl *ReplacementFragment::insertFragmentForTestRendering()
2443 NodeImpl *body = m_document->body();
2447 ElementImpl *holder = createDefaultParagraphElement(m_document);
2450 int exceptionCode = 0;
2451 holder->appendChild(m_fragment, exceptionCode);
2452 ASSERT(exceptionCode == 0);
2454 body->appendChild(holder, exceptionCode);
2455 ASSERT(exceptionCode == 0);
2458 m_document->updateLayout();
2463 void ReplacementFragment::restoreTestRenderingNodesToFragment(NodeImpl *holder)
2468 int exceptionCode = 0;
2469 while (NodeImpl *node = holder->firstChild()) {
2471 holder->removeChild(node, exceptionCode);
2472 ASSERT(exceptionCode == 0);
2473 m_fragment->appendChild(node, exceptionCode);
2474 ASSERT(exceptionCode == 0);
2479 void ReplacementFragment::computeStylesUsingTestRendering(NodeImpl *holder)
2484 m_document->updateLayout();
2486 for (NodeImpl *node = holder->firstChild(); node; node = node->traverseNextNode(holder))
2487 computeAndStoreNodeDesiredStyle(node, m_styles);
2490 void ReplacementFragment::removeUnrenderedNodesUsingTestRendering(NodeImpl *holder)
2495 QPtrList<NodeImpl> unrendered;
2497 for (NodeImpl *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
2498 if (!isNodeRendered(node) && !isTableStructureNode(node))
2499 unrendered.append(node);
2502 for (QPtrListIterator<NodeImpl> it(unrendered); it.current(); ++it)
2503 removeNode(it.current());
2506 int ReplacementFragment::countRenderedBlocks(NodeImpl *holder)
2513 for (NodeImpl *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
2514 if (node->isBlockFlow()) {
2521 NodeImpl *block = node->enclosingBlockFlowElement();
2522 if (block != prev) {
2532 void ReplacementFragment::removeStyleNodes()
2534 // Since style information has been computed and cached away in
2535 // computeStylesForNodes(), these style nodes can be removed, since
2536 // the correct styles will be added back in fixupNodeStyles().
2537 NodeImpl *node = m_fragment->firstChild();
2539 NodeImpl *next = node->traverseNextNode();
2540 // This list of tags change the appearance of content
2541 // in ways we can add back on later with CSS, if necessary.
2542 if (node->id() == ID_B ||
2543 node->id() == ID_BIG ||
2544 node->id() == ID_CENTER ||
2545 node->id() == ID_FONT ||
2546 node->id() == ID_I ||
2547 node->id() == ID_S ||
2548 node->id() == ID_SMALL ||
2549 node->id() == ID_STRIKE ||
2550 node->id() == ID_SUB ||
2551 node->id() == ID_SUP ||
2552 node->id() == ID_TT ||
2553 node->id() == ID_U ||
2554 isStyleSpan(node)) {
2555 removeNodePreservingChildren(node);
2557 else if (node->isHTMLElement()) {
2558 HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
2559 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
2560 if (inlineStyleDecl) {
2561 inlineStyleDecl->removeBlockProperties();
2562 inlineStyleDecl->removeInheritableProperties();
2569 NodeDesiredStyle::NodeDesiredStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style)
2570 : m_node(node), m_style(style)
2578 NodeDesiredStyle::NodeDesiredStyle(const NodeDesiredStyle &other)
2579 : m_node(other.node()), m_style(other.style())
2587 NodeDesiredStyle::~NodeDesiredStyle()
2595 NodeDesiredStyle &NodeDesiredStyle::operator=(const NodeDesiredStyle &other)
2597 NodeImpl *oldNode = m_node;
2598 CSSMutableStyleDeclarationImpl *oldStyle = m_style;
2600 m_node = other.node();
2601 m_style = other.style();
2616 ReplaceSelectionCommand::ReplaceSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, bool selectReplacement, bool smartReplace, bool matchStyle)
2617 : CompositeEditCommand(document),
2618 m_fragment(document, fragment, matchStyle),
2619 m_firstNodeInserted(0),
2620 m_lastNodeInserted(0),
2621 m_lastTopNodeInserted(0),
2622 m_insertionStyle(0),
2623 m_selectReplacement(selectReplacement),
2624 m_smartReplace(smartReplace),
2625 m_matchStyle(matchStyle)
2629 ReplaceSelectionCommand::~ReplaceSelectionCommand()
2631 if (m_firstNodeInserted)
2632 m_firstNodeInserted->deref();
2633 if (m_lastNodeInserted)
2634 m_lastNodeInserted->deref();
2635 if (m_lastTopNodeInserted)
2636 m_lastTopNodeInserted->deref();
2637 if (m_insertionStyle)
2638 m_insertionStyle->deref();
2641 void ReplaceSelectionCommand::doApply()
2643 // collect information about the current selection, prior to deleting the selection
2644 Selection selection = endingSelection();
2645 ASSERT(selection.isCaretOrRange());
2647 VisiblePosition visibleStart(selection.start(), selection.startAffinity());
2648 VisiblePosition visibleEnd(selection.end(), selection.endAffinity());
2649 bool startAtStartOfBlock = isStartOfBlock(visibleStart);
2650 bool startAtEndOfBlock = isEndOfBlock(visibleStart);
2651 bool startAtBlockBoundary = startAtStartOfBlock || startAtEndOfBlock;
2652 NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement();
2653 NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement();
2655 // decide whether to later merge content into the startBlock
2656 bool mergeStart = false;
2657 if (startBlock == startBlock->rootEditableElement() && startAtStartOfBlock && startAtEndOfBlock) {
2658 // empty editable subtree, need to mergeStart so that fragment ends up
2659 // inside the editable subtree rather than just before it
2662 // merge if current selection starts inside a paragraph, or there is only one block and no interchange newline to add
2663 mergeStart = !m_fragment.hasInterchangeNewlineAtStart() &&
2664 (!isStartOfParagraph(visibleStart) || (!m_fragment.hasInterchangeNewlineAtEnd() && !m_fragment.hasMoreThanOneBlock())) &&
2665 !isLastVisiblePositionInSpecialElement(selection.start());
2667 // This is a workaround for this bug:
2668 // <rdar://problem/4013642> REGRESSION (Mail): Copied quoted word does not paste as a quote if pasted at the start of a line
2669 // We need more powerful logic in this whole mergeStart code for this case to come out right without
2670 // breaking other cases.
2671 if (isStartOfParagraph(visibleStart) && isMailBlockquote(m_fragment.firstChild()))
2675 // decide whether to later append nodes to the end
2676 NodeImpl *beyondEndNode = 0;
2677 if (!isEndOfParagraph(visibleEnd) && !m_fragment.hasInterchangeNewlineAtEnd()) {
2678 Position beyondEndPos = selection.end().downstream();
2679 if (!isFirstVisiblePositionInSpecialElement(beyondEndPos))
2680 beyondEndNode = beyondEndPos.node();
2682 bool moveNodesAfterEnd = beyondEndNode && (startBlock != endBlock || m_fragment.hasMoreThanOneBlock());
2684 Position startPos = selection.start();
2686 // delete the current range selection, or insert paragraph for caret selection, as needed
2687 if (selection.isRange()) {
2688 deleteSelection(false, !(m_fragment.hasInterchangeNewlineAtStart() || m_fragment.hasInterchangeNewlineAtEnd() || m_fragment.hasMoreThanOneBlock()));
2689 document()->updateLayout();
2690 visibleStart = VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY);
2691 if (m_fragment.hasInterchangeNewlineAtStart()) {
2692 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
2693 if (!isEndOfDocument(visibleStart))
2694 setEndingSelection(visibleStart.next());
2697 insertParagraphSeparator();
2698 setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY));
2701 startPos = endingSelection().start();
2704 ASSERT(selection.isCaret());
2705 if (m_fragment.hasInterchangeNewlineAtStart()) {
2706 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
2707 if (!isEndOfDocument(visibleStart))
2708 setEndingSelection(visibleStart.next());
2711 insertParagraphSeparator();
2712 setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY));
2715 if (!m_fragment.hasInterchangeNewlineAtEnd() && m_fragment.hasMoreThanOneBlock() &&
2716 !startAtBlockBoundary && !isEndOfParagraph(visibleEnd)) {
2717 // The start and the end need to wind up in separate blocks.
2718 // Insert a paragraph separator to make that happen.
2719 insertParagraphSeparator();
2720 setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY).previous());
2722 startPos = endingSelection().start();
2725 if (startAtStartOfBlock && startBlock->inDocument())
2726 startPos = Position(startBlock, 0);
2728 startPos = positionOutsideContainingSpecialElement(startPos);
2730 KHTMLPart *part = document()->part();
2732 m_insertionStyle = styleAtPosition(startPos);
2733 m_insertionStyle->ref();
2736 // FIXME: Improve typing style.
2737 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2738 part->clearTypingStyle();
2741 // done if there is nothing to add
2742 if (!m_fragment.firstChild())
2745 // check for a line placeholder, and store it away for possible removal later.
2746 NodeImpl *block = startPos.node()->enclosingBlockFlowElement();
2747 NodeImpl *linePlaceholder = findBlockPlaceholder(block);
2748 if (!linePlaceholder) {
2749 Position downstream = startPos.downstream();
2750 downstream = positionOutsideContainingSpecialElement(downstream);
2751 if (downstream.node()->id() == ID_BR && downstream.offset() == 0 &&
2752 m_fragment.hasInterchangeNewlineAtEnd() &&
2753 isStartOfLine(VisiblePosition(downstream, VP_DEFAULT_AFFINITY)))
2754 linePlaceholder = downstream.node();
2757 // check whether to "smart replace" needs to add leading and/or trailing space
2758 bool addLeadingSpace = false;
2759 bool addTrailingSpace = false;
2760 // FIXME: We need the affinity for startPos and endPos, but Position::downstream
2761 // and Position::upstream do not give it
2762 if (m_smartReplace) {
2763 VisiblePosition visiblePos = VisiblePosition(startPos, VP_DEFAULT_AFFINITY);
2764 assert(visiblePos.isNotNull());
2765 addLeadingSpace = startPos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isStartOfLine(visiblePos);
2766 if (addLeadingSpace) {
2767 QChar previousChar = visiblePos.previous().character();
2768 if (!previousChar.isNull()) {
2769 addLeadingSpace = !part->isCharacterSmartReplaceExempt(previousChar, true);
2772 addTrailingSpace = startPos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isEndOfLine(visiblePos);
2773 if (addTrailingSpace) {
2774 QChar thisChar = visiblePos.character();
2775 if (!thisChar.isNull()) {
2776 addTrailingSpace = !part->isCharacterSmartReplaceExempt(thisChar, false);
2781 // There are five steps to adding the content: merge blocks at start, add remaining blocks,
2782 // add "smart replace" space, handle trailing newline, clean up.
2784 // initially, we say the insertion point is the start of selection
2785 document()->updateLayout();
2786 Position insertionPos = startPos;
2788 // step 1: merge content into the start block, if that is needed
2789 if (mergeStart && !isFirstVisiblePositionInSpecialElementInFragment(Position(m_fragment.mergeStartNode(), 0))) {
2790 NodeImpl *refNode = m_fragment.mergeStartNode();
2792 NodeImpl *node = refNode ? refNode->nextSibling() : 0;
2793 insertNodeAtAndUpdateNodesInserted(refNode, startPos.node(), startPos.offset());
2794 while (node && !isProbablyBlock(node)) {
2795 NodeImpl *next = node->nextSibling();
2796 insertNodeAfterAndUpdateNodesInserted(node, refNode);
2802 // update insertion point to be at the end of the last block inserted
2803 if (m_lastNodeInserted) {
2804 document()->updateLayout();
2805 insertionPos = Position(m_lastNodeInserted, m_lastNodeInserted->caretMaxOffset());
2809 // prune empty nodes from fragment
2810 // NOTE: why was this not done earlier, before the mergeStart?
2811 m_fragment.pruneEmptyNodes();
2813 // step 2 : merge everything remaining in the fragment
2814 if (m_fragment.firstChild()) {
2815 NodeImpl *refNode = m_fragment.firstChild();
2816 NodeImpl *node = refNode ? refNode->nextSibling() : 0;
2817 NodeImpl *insertionBlock = insertionPos.node()->enclosingBlockFlowElement();
2818 bool insertionBlockIsRoot = insertionBlock == insertionBlock->rootEditableElement();
2819 VisiblePosition visiblePos(insertionPos, DOWNSTREAM);
2820 if (!insertionBlockIsRoot && isProbablyBlock(refNode) && isStartOfBlock(visiblePos))
2821 insertNodeBeforeAndUpdateNodesInserted(refNode, insertionBlock);
2822 else if (!insertionBlockIsRoot && isProbablyBlock(refNode) && isEndOfBlock(visiblePos)) {
2823 insertNodeAfterAndUpdateNodesInserted(refNode, insertionBlock);
2824 } else if (mergeStart && !isProbablyBlock(refNode)) {
2825 Position pos = visiblePos.next().deepEquivalent().downstream();
2826 insertNodeAtAndUpdateNodesInserted(refNode, pos.node(), pos.offset());
2828 insertNodeAtAndUpdateNodesInserted(refNode, insertionPos.node(), insertionPos.offset());
2832 NodeImpl *next = node->nextSibling();
2833 insertNodeAfterAndUpdateNodesInserted(node, refNode);
2837 document()->updateLayout();
2838 insertionPos = Position(m_lastNodeInserted, m_lastNodeInserted->caretMaxOffset());
2841 // step 3 : handle "smart replace" whitespace
2842 if (addTrailingSpace && m_lastNodeInserted) {
2843 document()->updateLayout();
2844 Position pos(m_lastNodeInserted, m_lastNodeInserted->caretMaxOffset());
2845 bool needsTrailingSpace = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull();
2846 if (needsTrailingSpace) {
2847 if (m_lastNodeInserted->isTextNode()) {
2848 TextImpl *text = static_cast<TextImpl *>(m_lastNodeInserted);
2849 insertTextIntoNode(text, text->length(), nonBreakingSpaceString());
2850 insertionPos = Position(text, text->length());
2853 NodeImpl *node = document()->createEditingTextNode(nonBreakingSpaceString());
2854 insertNodeAfterAndUpdateNodesInserted(node, m_lastNodeInserted);
2855 insertionPos = Position(node, 1);
2860 if (addLeadingSpace && m_firstNodeInserted) {
2861 document()->updateLayout();
2862 Position pos(m_firstNodeInserted, 0);
2863 bool needsLeadingSpace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull();
2864 if (needsLeadingSpace) {
2865 if (m_firstNodeInserted->isTextNode()) {
2866 TextImpl *text = static_cast<TextImpl *>(m_firstNodeInserted);
2867 insertTextIntoNode(text, 0, nonBreakingSpaceString());
2869 NodeImpl *node = document()->createEditingTextNode(nonBreakingSpaceString());
2870 insertNodeBeforeAndUpdateNodesInserted(node, m_firstNodeInserted);
2875 Position lastPositionToSelect;
2877 // step 4 : handle trailing newline
2878 if (m_fragment.hasInterchangeNewlineAtEnd()) {
2879 removeLinePlaceholderIfNeeded(linePlaceholder);
2881 if (!m_lastNodeInserted) {
2882 lastPositionToSelect = endingSelection().end().downstream();
2885 bool insertParagraph = false;
2886 VisiblePosition pos(insertionPos, VP_DEFAULT_AFFINITY);
2888 if (startBlock == endBlock && !isProbablyBlock(m_lastTopNodeInserted)) {
2889 insertParagraph = true;
2891 // Handle end-of-document case.
2892 document()->updateLayout();
2893 if (isEndOfDocument(pos))
2894 insertParagraph = true;
2896 if (insertParagraph) {
2897 setEndingSelection(insertionPos, DOWNSTREAM);
2898 insertParagraphSeparator();
2899 VisiblePosition next = pos.next();
2901 // Select up to the paragraph separator that was added.
2902 lastPositionToSelect = next.deepEquivalent().downstream();
2903 updateNodesInserted(lastPositionToSelect.node());
2905 // Select up to the preexising paragraph separator.
2906 VisiblePosition next = pos.next();
2907 lastPositionToSelect = next.deepEquivalent().downstream();
2912 if (m_lastNodeInserted && m_lastNodeInserted->id() == ID_BR && !document()->inStrictMode()) {
2913 document()->updateLayout();
2914 VisiblePosition pos(Position(m_lastNodeInserted, 1), DOWNSTREAM);
2915 if (isEndOfBlock(pos)) {
2916 NodeImpl *next = m_lastNodeInserted->traverseNextNode();
2917 bool hasTrailingBR = next && next->id() == ID_BR && m_lastNodeInserted->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
2918 if (!hasTrailingBR) {
2919 // Insert an "extra" BR at the end of the block.
2920 insertNodeBefore(createBreakElement(document()), m_lastNodeInserted);
2925 if (moveNodesAfterEnd && !isLastVisiblePositionInSpecialElement(Position(m_lastNodeInserted, maxRangeOffset(m_lastNodeInserted)))) {
2926 document()->updateLayout();
2927 QValueList<NodeDesiredStyle> styles;
2928 QPtrList<NodeImpl> blocks;
2929 NodeImpl *node = beyondEndNode;
2930 NodeImpl *refNode = m_lastNodeInserted;
2932 RenderObject *renderer = node->renderer();
2933 // Stop at the first table or block.
2934 if (renderer && (renderer->isBlockFlow() || renderer->isTable()))
2936 NodeImpl *next = node->nextSibling();
2937 blocks.append(node->enclosingBlockFlowElement());
2938 computeAndStoreNodeDesiredStyle(node, styles);
2940 // No need to update inserted node variables.
2941 insertNodeAfter(node, refNode);
2943 // We want to move the first BR we see, so check for that here.
2944 if (node->id() == ID_BR)
2948 document()->updateLayout();
2949 for (QPtrListIterator<NodeImpl> it(blocks); it.current(); ++it) {
2950 NodeImpl *blockToRemove = it.current();
2951 if (!blockToRemove->inDocument())
2953 if (!blockToRemove->renderer() || !blockToRemove->renderer()->firstChild()) {
2954 if (blockToRemove->parentNode())
2955 blocks.append(blockToRemove->parentNode()->enclosingBlockFlowElement());
2956 removeNode(blockToRemove);
2957 document()->updateLayout();
2961 fixupNodeStyles(styles);
2966 fixupNodeStyles(m_fragment.desiredStyles());
2967 completeHTMLReplacement(lastPositionToSelect);
2970 removeLinePlaceholderIfNeeded(linePlaceholder);
2973 void ReplaceSelectionCommand::removeLinePlaceholderIfNeeded(NodeImpl *linePlaceholder)
2975 if (!linePlaceholder)
2978 document()->updateLayout();
2979 if (linePlaceholder->inDocument()) {
2980 VisiblePosition placeholderPos(linePlaceholder, linePlaceholder->renderer()->caretMinOffset(), DOWNSTREAM);
2981 if (placeholderPos.next().isNull() ||
2982 !(isStartOfLine(placeholderPos) && isEndOfLine(placeholderPos))) {
2983 NodeImpl *block = linePlaceholder->enclosingBlockFlowElement();
2984 removeNode(linePlaceholder);
2985 document()->updateLayout();
2986 if (!block->renderer() || block->renderer()->height() == 0)
2992 void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
2997 if (m_firstNodeInserted && m_firstNodeInserted->inDocument() &&
2998 m_lastNodeInserted && m_lastNodeInserted->inDocument()) {
3000 // Find the last leaf.
3001 NodeImpl *lastLeaf = m_lastNodeInserted;
3003 NodeImpl *nextChild = lastLeaf->lastChild();
3006 lastLeaf = nextChild;
3009 // Find the first leaf.
3010 NodeImpl *firstLeaf = m_firstNodeInserted;
3012 NodeImpl *nextChild = firstLeaf->firstChild();
3015 firstLeaf = nextChild;
3018 // Call updateLayout so caretMinOffset and caretMaxOffset return correct values.
3019 document()->updateLayout();
3020 start = Position(firstLeaf, firstLeaf->caretMinOffset());
3021 end = Position(lastLeaf, lastLeaf->caretMaxOffset());
3024 assert(m_insertionStyle);
3025 setEndingSelection(Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY));
3026 applyStyle(m_insertionStyle);
3029 if (lastPositionToSelect.isNotNull())
3030 end = lastPositionToSelect;
3032 else if (lastPositionToSelect.isNotNull()) {
3033 start = end = lastPositionToSelect;
3039 if (m_selectReplacement)
3040 setEndingSelection(Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY));
3042 setEndingSelection(end, SEL_DEFAULT_AFFINITY);
3044 rebalanceWhitespace();
3047 EditAction ReplaceSelectionCommand::editingAction() const
3049 return EditActionPaste;
3052 void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(NodeImpl *insertChild, NodeImpl *refChild)
3054 insertNodeAfter(insertChild, refChild);
3055 updateNodesInserted(insertChild);
3058 void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(NodeImpl *insertChild, NodeImpl *refChild, long offset)
3060 insertNodeAt(insertChild, refChild, offset);
3061 updateNodesInserted(insertChild);
3064 void ReplaceSelectionCommand::insertNodeBeforeAndUpdateNodesInserted(NodeImpl *insertChild, NodeImpl *refChild)
3066 insertNodeBefore(insertChild, refChild);
3067 updateNodesInserted(insertChild);
3070 void ReplaceSelectionCommand::updateNodesInserted(NodeImpl *node)
3075 // update m_lastTopNodeInserted
3077 if (m_lastTopNodeInserted)
3078 m_lastTopNodeInserted->deref();
3079 m_lastTopNodeInserted = node;
3081 // update m_firstNodeInserted
3082 if (!m_firstNodeInserted) {
3083 m_firstNodeInserted = node;
3084 m_firstNodeInserted->ref();
3087 if (node == m_lastNodeInserted)
3090 // update m_lastNodeInserted
3091 NodeImpl *old = m_lastNodeInserted;
3092 m_lastNodeInserted = node->lastDescendent();
3093 m_lastNodeInserted->ref();
3098 void ReplaceSelectionCommand::fixupNodeStyles(const QValueList<NodeDesiredStyle> &list)
3100 // This function uses the mapped "desired style" to apply the additional style needed, if any,
3101 // to make the node have the desired style.
3103 document()->updateLayout();
3105 QValueListConstIterator<NodeDesiredStyle> it;
3106 for (it = list.begin(); it != list.end(); ++it) {
3107 NodeImpl *node = (*it).node();
3108 CSSMutableStyleDeclarationImpl *desiredStyle = (*it).style();
3109 ASSERT(desiredStyle);
3111 if (!node->inDocument())
3114 // The desiredStyle declaration tells what style this node wants to be.
3115 // Compare that to the style that it is right now in the document.
3116 Position pos(node, 0);
3117 CSSComputedStyleDeclarationImpl *currentStyle = pos.computedStyle();
3118 currentStyle->ref();
3120 // Check for the special "match nearest blockquote color" property and resolve to the correct
3121 // color if necessary.
3122 DOMString matchColorCheck = desiredStyle->getPropertyValue(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
3123 if (matchColorCheck == matchNearestBlockquoteColorString()) {
3124 NodeImpl *blockquote = nearestMailBlockquote(node);
3125 Position pos(blockquote ? blockquote : node->getDocument()->documentElement(), 0);
3126 CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
3127 DOMString desiredColor = desiredStyle->getPropertyValue(CSS_PROP_COLOR);
3128 DOMString nearestColor = style->getPropertyValue(CSS_PROP_COLOR);
3129 if (desiredColor != nearestColor)
3130 desiredStyle->setProperty(CSS_PROP_COLOR, nearestColor);
3132 desiredStyle->removeProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
3134 currentStyle->diff(desiredStyle);
3136 // Only add in block properties if the node is at the start of a
3137 // paragraph. This matches AppKit.
3138 if (!isStartOfParagraph(VisiblePosition(pos, DOWNSTREAM)))
3139 desiredStyle->removeBlockProperties();
3141 // If the desiredStyle is non-zero length, that means the current style differs
3142 // from the desired by the styles remaining in the desiredStyle declaration.
3143 if (desiredStyle->length() > 0) {
3144 DOM::RangeImpl *rangeAroundNode = document()->createRange();
3145 rangeAroundNode->ref();
3146 int exceptionCode = 0;
3147 rangeAroundNode->selectNode(node, exceptionCode);
3148 ASSERT(exceptionCode == 0);
3149 // affinity is not really important since this is a temp selection
3150 // just for calling applyStyle
3151 setEndingSelection(Selection(rangeAroundNode, SEL_DEFAULT_AFFINITY, SEL_DEFAULT_AFFINITY));
3152 applyStyle(desiredStyle);
3153 rangeAroundNode->deref();
3156 currentStyle->deref();
3160 void computeAndStoreNodeDesiredStyle(DOM::NodeImpl *node, QValueList<NodeDesiredStyle> &list)
3162 if (!node || !node->inDocument())
3165 CSSComputedStyleDeclarationImpl *computedStyle = Position(node, 0).computedStyle();
3166 computedStyle->ref();
3167 CSSMutableStyleDeclarationImpl *style = computedStyle->copyInheritableProperties();
3168 list.append(NodeDesiredStyle(node, style));
3169 computedStyle->deref();
3171 // In either of the color-matching tests below, set the color to a pseudo-color that will
3172 // make the content take on the color of the nearest-enclosing blockquote (if any) after
3174 if (NodeImpl *blockquote = nearestMailBlockquote(node)) {
3175 CSSComputedStyleDeclarationImpl *blockquoteStyle = Position(blockquote, 0).computedStyle();
3176 if (blockquoteStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR)) {
3177 style->setProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
3181 NodeImpl *documentElement = node->getDocument() ? node->getDocument()->documentElement() : 0;
3182 if (documentElement) {
3183 CSSComputedStyleDeclarationImpl *documentStyle = Position(documentElement, 0).computedStyle();
3184 if (documentStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR)) {
3185 style->setProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
3190 //------------------------------------------------------------------------------------------
3191 // SetNodeAttributeCommand
3193 SetNodeAttributeCommand::SetNodeAttributeCommand(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value)
3194 : EditCommand(document), m_element(element), m_attribute(attribute), m_value(value)
3198 ASSERT(!m_value.isNull());
3201 SetNodeAttributeCommand::~SetNodeAttributeCommand()
3207 void SetNodeAttributeCommand::doApply()
3210 ASSERT(!m_value.isNull());
3212 int exceptionCode = 0;
3213 m_oldValue = m_element->getAttribute(m_attribute);
3214 m_element->setAttribute(m_attribute, m_value.implementation(), exceptionCode);
3215 ASSERT(exceptionCode == 0);
3218 void SetNodeAttributeCommand::doUnapply()
3222 int exceptionCode = 0;
3223 if (m_oldValue.isNull())
3224 m_element->removeAttribute(m_attribute, exceptionCode);
3226 m_element->setAttribute(m_attribute, m_oldValue.implementation(), exceptionCode);
3227 ASSERT(exceptionCode == 0);
3230 //------------------------------------------------------------------------------------------
3231 // SplitTextNodeCommand
3233 SplitTextNodeCommand::SplitTextNodeCommand(DocumentImpl *document, TextImpl *text, long offset)
3234 : EditCommand(document), m_text1(0), m_text2(text), m_offset(offset)
3237 ASSERT(m_text2->length() > 0);
3242 SplitTextNodeCommand::~SplitTextNodeCommand()
3251 void SplitTextNodeCommand::doApply()
3254 ASSERT(m_offset > 0);
3256 int exceptionCode = 0;
3258 // EDIT FIXME: This should use better smarts for figuring out which portion
3259 // of the split to copy (based on their comparitive sizes). We should also
3260 // just use the DOM's splitText function.
3263 // create only if needed.
3264 // if reapplying, this object will already exist.
3265 m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode));
3266 ASSERT(exceptionCode == 0);
3271 m_text2->deleteData(0, m_offset, exceptionCode);
3272 ASSERT(exceptionCode == 0);
3274 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
3275 ASSERT(exceptionCode == 0);
3277 ASSERT(m_text2->previousSibling()->isTextNode());
3278 ASSERT(m_text2->previousSibling() == m_text1);
3281 void SplitTextNodeCommand::doUnapply()
3286 ASSERT(m_text1->nextSibling() == m_text2);
3288 int exceptionCode = 0;
3289 m_text2->insertData(0, m_text1->data(), exceptionCode);
3290 ASSERT(exceptionCode == 0);
3292 m_text2->parentNode()->removeChild(m_text1, exceptionCode);
3293 ASSERT(exceptionCode == 0);
3295 m_offset = m_text1->length();
3298 //------------------------------------------------------------------------------------------
3299 // SplitElementCommand
3301 SplitElementCommand::SplitElementCommand(DOM::DocumentImpl *document, DOM::ElementImpl *element, DOM::NodeImpl *atChild)
3302 : EditCommand(document), m_element1(0), m_element2(element), m_atChild(atChild)
3311 SplitElementCommand::~SplitElementCommand()
3314 m_element1->deref();
3317 m_element2->deref();
3322 void SplitElementCommand::doApply()
3327 int exceptionCode = 0;
3330 // create only if needed.
3331 // if reapplying, this object will already exist.
3332 m_element1 = static_cast<ElementImpl *>(m_element2->cloneNode(false));
3337 m_element2->parent()->insertBefore(m_element1, m_element2, exceptionCode);
3338 ASSERT(exceptionCode == 0);
3340 while (m_element2->firstChild() != m_atChild) {
3341 ASSERT(m_element2->firstChild());
3342 m_element1->appendChild(m_element2->firstChild(), exceptionCode);
3343 ASSERT(exceptionCode == 0);
3347 void SplitElementCommand::doUnapply()
3353 ASSERT(m_element1->nextSibling() == m_element2);
3354 ASSERT(m_element2->firstChild() && m_element2->firstChild() == m_atChild);
3356 int exceptionCode = 0;
3358 while (m_element1->lastChild()) {
3359 m_element2->insertBefore(m_element1->lastChild(), m_element2->firstChild(), exceptionCode);
3360 ASSERT(exceptionCode == 0);
3363 m_element2->parentNode()->removeChild(m_element1, exceptionCode);
3364 ASSERT(exceptionCode == 0);
3367 //------------------------------------------------------------------------------------------
3368 // MergeIdenticalElementsCommand
3370 MergeIdenticalElementsCommand::MergeIdenticalElementsCommand(DOM::DocumentImpl *document, DOM::ElementImpl *first, DOM::ElementImpl *second)
3371 : EditCommand(document), m_element1(first), m_element2(second), m_atChild(0)
3380 MergeIdenticalElementsCommand::~MergeIdenticalElementsCommand()
3386 m_element1->deref();
3388 m_element2->deref();
3391 void MergeIdenticalElementsCommand::doApply()
3395 ASSERT(m_element1->nextSibling() == m_element2);
3397 int exceptionCode = 0;
3400 m_atChild = m_element2->firstChild();
3404 while (m_element1->lastChild()) {
3405 m_element2->insertBefore(m_element1->lastChild(), m_element2->firstChild(), exceptionCode);
3406 ASSERT(exceptionCode == 0);
3409 m_element2->parentNode()->removeChild(m_element1, exceptionCode);
3410 ASSERT(exceptionCode == 0);
3413 void MergeIdenticalElementsCommand::doUnapply()
3418 int exceptionCode = 0;
3420 m_element2->parent()->insertBefore(m_element1, m_element2, exceptionCode);
3421 ASSERT(exceptionCode == 0);
3423 while (m_element2->firstChild() != m_atChild) {
3424 ASSERT(m_element2->firstChild());
3425 m_element1->appendChild(m_element2->firstChild(), exceptionCode);
3426 ASSERT(exceptionCode == 0);
3430 //------------------------------------------------------------------------------------------
3431 // WrapContentsInDummySpanCommand
3433 WrapContentsInDummySpanCommand::WrapContentsInDummySpanCommand(DOM::DocumentImpl *document, DOM::ElementImpl *element)
3434 : EditCommand(document), m_element(element), m_dummySpan(0)
3441 WrapContentsInDummySpanCommand::~WrapContentsInDummySpanCommand()
3444 m_dummySpan->deref();
3450 void WrapContentsInDummySpanCommand::doApply()
3454 int exceptionCode = 0;