Reviewed by Ken and Harrison.
authordarin <darin@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 31 Jan 2005 18:03:15 +0000 (18:03 +0000)
committerdarin <darin@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 31 Jan 2005 18:03:15 +0000 (18:03 +0000)
        - fixed <rdar://problem/3947901> REGRESSION (Mail): Pasting paragraph of rich text leaves insertion point before pasted text
        - fixed <rdar://problem/3949790> hitting return after underlined line results in too much or too little underlined
        - fixed <rdar://problem/3981759> nil-deref and crash when pasting just a paragraph break
        - fixed a couple problems I discovered while working with bug 3949790

        * khtml/editing/htmlediting.cpp:
        (khtml::ApplyStyleCommand::applyInlineStyle): Pass StayInBlock to upstream. Without this, we end up going too far
        upstream in the test case in bug 3949790.
        (khtml::ApplyStyleCommand::removeInlineStyle): Pass StayInBlock to upstream and downstream. Same reason as above.
        (khtml::ReplaceSelectionCommand::doApply): Update endPos if inserting a new node and endPos is using that node's
        parent and an offset past the node being inserted. That change fixes a problem with the position of the insertion point
        after pasting into the top level of a document (from test cases in 3947901 and 3949790). When setting insertionPos, use
        code that works when lastNodeInserted is a block rather than a text node. That change fixes a problem where a newline is
        not added when pasting an entire paragraph into the end of a document (from test case in 3949790). Added nil check before
        checking if lastNodeInserted is a <br> element, which fixes the crash when pasting just a paragraph break.

        * khtml/editing/visible_units.h: Filled out the set of calls to add some boolean checks for lines (needed for the
        bug fix), and calls for blocks (not yet implemented), and documents. The document checks may need refinement to
        properly handle documents with a mix of editable and non-editable content, but for now they just refactor code
        and make things a little clearer. Also removed the "include line break" parameter from endOfSentence.
        * khtml/editing/visible_units.cpp:
        (khtml::rootBoxForLine): Added.
        (khtml::startOfLine): Added. Algorithm taken from selectionForLine in selection.cpp.
        (khtml::endOfLine): Ditto.
        (khtml::inSameLine): Added.
        (khtml::isStartOfLine): Added.
        (khtml::isEndOfLine): Added.
        (khtml::endOfSentence): Removed "include line break" parameter.
        (khtml::inSameParagraph): Added a null check.
        (khtml::isStartOfParagraph): Ditto.
        (khtml::isEndOfParagraph): Ditto.
        (khtml::startOfBlock): Added.
        (khtml::endOfBlock): Added.
        (khtml::inSameBlock): Added.
        (khtml::isStartOfBlock): Added.
        (khtml::isEndOfBlock): Added.
        (khtml::startOfDocument): Added.
        (khtml::endOfDocument): Added.
        (khtml::inSameDocument): Added.
        (khtml::isStartOfDocument): Added.
        (khtml::isEndOfDocument): Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@8480 268f45cc-cd09-0410-ab3c-d52691b4dbfc

WebCore/ChangeLog-2005-08-23
WebCore/khtml/editing/htmlediting.cpp
WebCore/khtml/editing/visible_units.cpp
WebCore/khtml/editing/visible_units.h

index 872a86c605d24b91181751a1b7a1cf13c7cd83ed..19b405652b0d0214a9329c81ac564b40f21690c0 100644 (file)
@@ -1,3 +1,49 @@
+2005-01-31  Darin Adler  <darin@apple.com>
+
+        Reviewed by Ken and Harrison.
+
+        - fixed <rdar://problem/3947901> REGRESSION (Mail): Pasting paragraph of rich text leaves insertion point before pasted text
+        - fixed <rdar://problem/3949790> hitting return after underlined line results in too much or too little underlined
+        - fixed <rdar://problem/3981759> nil-deref and crash when pasting just a paragraph break
+        - fixed a couple problems I discovered while working with bug 3949790
+
+        * khtml/editing/htmlediting.cpp:
+        (khtml::ApplyStyleCommand::applyInlineStyle): Pass StayInBlock to upstream. Without this, we end up going too far
+        upstream in the test case in bug 3949790.
+        (khtml::ApplyStyleCommand::removeInlineStyle): Pass StayInBlock to upstream and downstream. Same reason as above.
+        (khtml::ReplaceSelectionCommand::doApply): Update endPos if inserting a new node and endPos is using that node's
+        parent and an offset past the node being inserted. That change fixes a problem with the position of the insertion point
+        after pasting into the top level of a document (from test cases in 3947901 and 3949790). When setting insertionPos, use
+        code that works when lastNodeInserted is a block rather than a text node. That change fixes a problem where a newline is
+        not added when pasting an entire paragraph into the end of a document (from test case in 3949790). Added nil check before
+        checking if lastNodeInserted is a <br> element, which fixes the crash when pasting just a paragraph break.
+
+        * khtml/editing/visible_units.h: Filled out the set of calls to add some boolean checks for lines (needed for the
+        bug fix), and calls for blocks (not yet implemented), and documents. The document checks may need refinement to
+        properly handle documents with a mix of editable and non-editable content, but for now they just refactor code
+        and make things a little clearer. Also removed the "include line break" parameter from endOfSentence.
+        * khtml/editing/visible_units.cpp:
+        (khtml::rootBoxForLine): Added.
+        (khtml::startOfLine): Added. Algorithm taken from selectionForLine in selection.cpp.
+        (khtml::endOfLine): Ditto.
+        (khtml::inSameLine): Added.
+        (khtml::isStartOfLine): Added.
+        (khtml::isEndOfLine): Added.
+        (khtml::endOfSentence): Removed "include line break" parameter.
+        (khtml::inSameParagraph): Added a null check.
+        (khtml::isStartOfParagraph): Ditto.
+        (khtml::isEndOfParagraph): Ditto.
+        (khtml::startOfBlock): Added.
+        (khtml::endOfBlock): Added.
+        (khtml::inSameBlock): Added.
+        (khtml::isStartOfBlock): Added.
+        (khtml::isEndOfBlock): Added.
+        (khtml::startOfDocument): Added.
+        (khtml::endOfDocument): Added.
+        (khtml::inSameDocument): Added.
+        (khtml::isStartOfDocument): Added.
+        (khtml::isEndOfDocument): Added.
+
 2005-01-30  Darin Adler  <darin@apple.com>
 
         Reviewed by John.
index 719a356963ef9fcad6feaf6b862a07a727f75161..71ad0b4096bb47379b60ab148ce60a4e182c57d2 100644 (file)
@@ -1021,7 +1021,7 @@ void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Posi
             ASSERT(paragraphStart.node()->isAncestor(paragraphEnd.node()->enclosingBlockFlowElement()));
             return;
         }
-        else if (visibleParagraphEnd.next().isNull()) {
+        else if (isEndOfDocument(visibleParagraphEnd)) {
             // At the end of the document. We can bail here as well.
             return;
         }
@@ -1364,7 +1364,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style)
     // This will ensure we remove all traces of the relevant styles from the selection
     // and prevent us from adding redundant ones, as described in:
     // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
-    removeInlineStyle(style, start.upstream(), end);
+    removeInlineStyle(style, start.upstream(StayInBlock), end);
 
     if (splitStart || splitEnd) {
         cleanUpEmptyStyleSpans(start, end);
@@ -1683,7 +1683,7 @@ void ApplyStyleCommand::removeInlineStyle(CSSMutableStyleDeclarationImpl *style,
     CSSValueImpl *textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT);
 
     if (textDecorationSpecialProperty) {
-        pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream());
+        pushDownTextDecorationStyleAtBoundaries(start.downstream(StayInBlock), end.upstream(StayInBlock));
         style = style->copy();
         style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT));
     }
@@ -2342,7 +2342,7 @@ void DeleteSelectionCommand::handleGeneralDelete()
         //
         NodeImpl *old = m_startNode;
         VisiblePosition visibleEnd = VisiblePosition(m_downstreamEnd);
-        if (visibleEnd.next().isNull() && !isFirstVisiblePositionOnLine(visibleEnd)) {
+        if (isEndOfDocument(visibleEnd) && !isFirstVisiblePositionOnLine(visibleEnd)) {
             m_startNode = m_startBlock->firstChild();
         }
         else {
@@ -4264,8 +4264,11 @@ void ReplaceSelectionCommand::doApply()
         }
         else if (!mergeEnd && !insertionNodeIsBody && isProbablyBlock(refNode) && isEndOfParagraph(visiblePos))
             insertNodeAfter(refNode, insertionPos.node());
-        else
+        else {
             insertNodeAt(refNode, insertionPos.node(), insertionPos.offset());
+            if (insertionPos.node() == endPos.node() && insertionPos.offset() <= endPos.offset())
+                endPos = Position(endPos.node(), endPos.offset() + 1);
+        }
         if (!firstNodeInserted)
             firstNodeInserted = refNode;
         if (!lastNodeInsertedInMergeEnd)
@@ -4279,7 +4282,12 @@ void ReplaceSelectionCommand::doApply()
             node = next;
         }
         document()->updateLayout();
-        insertionPos = Position(lastNodeInserted, lastNodeInserted->caretMaxOffset());
+        if (lastNodeInserted->isTextNode())
+            insertionPos = Position(lastNodeInserted, lastNodeInserted->caretMaxOffset());
+        else if (lastNodeInserted->childNodeCount() > 0)
+            insertionPos = Position(lastNodeInserted, lastNodeInserted->childNodeCount());
+        else
+            insertionPos = Position(lastNodeInserted->parentNode(), lastNodeInserted->nodeIndex() + 1);
     }
 
     // Handle "smart replace" whitespace
@@ -4315,8 +4323,7 @@ void ReplaceSelectionCommand::doApply()
 
     // Handle trailing newline
     if (m_fragment.hasInterchangeNewline()) {
-        if ((startBlock == endBlock) && (VisiblePosition(lastNodeInserted, lastNodeInserted->caretMaxOffset()).next().isNull())) {
-        
+        if (startBlock == endBlock && isEndOfDocument(VisiblePosition(insertionPos))) {
             setEndingSelection(insertionPos);
             insertParagraphSeparator();
             endPos = endingSelection().end().downstream();
@@ -4324,7 +4331,7 @@ void ReplaceSelectionCommand::doApply()
         completeHTMLReplacement(startPos, endPos);
     }
     else {
-        if (lastNodeInserted->id() == ID_BR && !document()->inStrictMode()) {
+        if (lastNodeInserted && lastNodeInserted->id() == ID_BR && !document()->inStrictMode()) {
             document()->updateLayout();
             VisiblePosition pos(Position(lastNodeInserted, 0));
             if (isLastVisiblePositionInBlock(pos)) {
@@ -4782,7 +4789,7 @@ void TypingCommand::forwardDeleteKeyPressed(DocumentImpl *document, bool smartDe
     }
     else {
         Selection selection = part->selection();
-        if (selection.isCaret() && VisiblePosition(selection.start()).next().isNull()) {
+        if (selection.isCaret() && isEndOfDocument(VisiblePosition(selection.start()))) {
             // do nothing for a delete key at the start of an editable element.
         }
         else {
index d5da009ca977c434bb64971adef0327057b563c9..3bae87586474d772b573febf2ce0b9860ecddb0d 100644 (file)
@@ -229,16 +229,104 @@ VisiblePosition nextWordPosition(const VisiblePosition &c)
 
 // ---------
 
+static RootInlineBox *rootBoxForLine(const VisiblePosition &c, EAffinity affinity)
+{
+    Position p = c.deepEquivalent();
+    NodeImpl *node = p.node();
+    if (!node)
+        return 0;
+
+    RenderObject *renderer = node->renderer();
+    if (!renderer)
+        return 0;
+    
+    InlineBox *box = renderer->inlineBox(p.offset(), affinity);
+    if (!box)
+        return 0;
+    
+    return box->root();
+}
+
+VisiblePosition startOfLine(const VisiblePosition &c, EAffinity affinity)
+{
+    RootInlineBox *rootBox = rootBoxForLine(c, affinity);
+    if (!rootBox)
+        return VisiblePosition();
+    
+    InlineBox *startBox = rootBox->firstChild();
+    if (!startBox)
+        return VisiblePosition();
+
+    RenderObject *startRenderer = startBox->object();
+    if (!startRenderer)
+        return VisiblePosition();
+
+    NodeImpl *startNode = startRenderer->element();
+    if (!startNode)
+        return VisiblePosition();
+
+    long startOffset = 0;
+    if (startBox->isInlineTextBox()) {
+        InlineTextBox *startTextBox = static_cast<InlineTextBox *>(startBox);
+        startOffset = startTextBox->m_start;
+    }
+    return VisiblePosition(startNode, startOffset);
+}
+
+VisiblePosition endOfLine(const VisiblePosition &c, EAffinity affinity, EIncludeLineBreak includeLineBreak)
+{
+    // FIXME: Need to implement the "include line break" version.
+    assert(includeLineBreak == DoNotIncludeLineBreak);
+
+    RootInlineBox *rootBox = rootBoxForLine(c, affinity);
+    if (!rootBox)
+        return VisiblePosition();
+    
+    InlineBox *endBox = rootBox->lastChild();
+    if (!endBox)
+        return VisiblePosition();
+
+    RenderObject *endRenderer = endBox->object();
+    if (!endRenderer)
+        return VisiblePosition();
+
+    NodeImpl *endNode = endRenderer->element();
+    if (!endNode)
+        return VisiblePosition();
+
+    long endOffset = 1;
+    if (endBox->isInlineTextBox()) {
+        InlineTextBox *endTextBox = static_cast<InlineTextBox *>(endBox);
+        endOffset = endTextBox->m_start + endTextBox->m_len;
+    }
+    return VisiblePosition(endNode, endOffset);
+}
+
+bool inSameLine(const VisiblePosition &a, EAffinity aa, const VisiblePosition &b, EAffinity ab)
+{
+    return a.isNotNull() && startOfLine(a, aa) == startOfLine(b, ab);
+}
+
+bool isStartOfLine(const VisiblePosition &p, EAffinity affinity)
+{
+    return p.isNotNull() && p == startOfLine(p, affinity);
+}
+
+bool isEndOfLine(const VisiblePosition &p, EAffinity affinity)
+{
+    return p.isNotNull() && p == endOfLine(p, affinity, DoNotIncludeLineBreak);
+}
+
 VisiblePosition previousLinePosition(const VisiblePosition &c, EAffinity affinity, int x)
 {
-    Position pos = affinity == UPSTREAM ? c.deepEquivalent() : c.downstreamDeepEquivalent();
-    return VisiblePosition(pos.previousLinePosition(x, affinity));
+    Position p = affinity == UPSTREAM ? c.deepEquivalent() : c.downstreamDeepEquivalent();
+    return VisiblePosition(p.previousLinePosition(x, affinity));
 }
 
 VisiblePosition nextLinePosition(const VisiblePosition &c, EAffinity affinity, int x)
 {
-    Position pos = affinity == UPSTREAM ? c.deepEquivalent() : c.downstreamDeepEquivalent();
-    return VisiblePosition(pos.nextLinePosition(x, affinity));
+    Position p = affinity == UPSTREAM ? c.deepEquivalent() : c.downstreamDeepEquivalent();
+    return VisiblePosition(p.nextLinePosition(x, affinity));
 }
 
 // ---------
@@ -262,7 +350,7 @@ static unsigned endSentenceBoundary(const QChar *characters, unsigned length)
     return end;
 }
 
-VisiblePosition endOfSentence(const VisiblePosition &c, EIncludeLineBreak includeLineBreak)
+VisiblePosition endOfSentence(const VisiblePosition &c)
 {
     return nextBoundary(c, endSentenceBoundary);
 }
@@ -389,17 +477,17 @@ VisiblePosition endOfParagraph(const VisiblePosition &c, EIncludeLineBreak inclu
 
 bool inSameParagraph(const VisiblePosition &a, const VisiblePosition &b)
 {
-    return a == b || startOfParagraph(a) == startOfParagraph(b);
+    return a.isNotNull() && startOfParagraph(a) == startOfParagraph(b);
 }
 
 bool isStartOfParagraph(const VisiblePosition &pos)
 {
-    return pos == startOfParagraph(pos);
+    return pos.isNotNull() && pos == startOfParagraph(pos);
 }
 
 bool isEndOfParagraph(const VisiblePosition &pos)
 {
-    return pos == endOfParagraph(pos, DoNotIncludeLineBreak);
+    return pos.isNotNull() && pos == endOfParagraph(pos, DoNotIncludeLineBreak);
 }
 
 VisiblePosition previousParagraphPosition(const VisiblePosition &p, EAffinity affinity, int x)
@@ -426,4 +514,156 @@ VisiblePosition nextParagraphPosition(const VisiblePosition &p, EAffinity affini
     return pos;
 }
 
+// ---------
+
+// written, but not yet tested
+VisiblePosition startOfBlock(const VisiblePosition &c)
+{
+    Position p = c.deepEquivalent();
+    NodeImpl *startNode = p.node();
+    if (!startNode)
+        return VisiblePosition();
+
+    NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
+
+    NodeImpl *node = startNode;
+    long offset = p.offset();
+
+    for (NodeImpl *n = startNode; n; n = n->traversePreviousNodePostOrder(startBlock)) {
+        RenderObject *r = n->renderer();
+        if (!r)
+            continue;
+        RenderStyle *style = r->style();
+        if (style->visibility() != VISIBLE)
+            continue;
+        if (r->isBlockFlow())
+            break;
+        if (r->isText()) {
+            node = n;
+            offset = 0;
+        } else if (r->isReplaced()) {
+            node = n;
+            offset = 0;
+        }
+    }
+
+    return VisiblePosition(node, offset);
+}
+
+// written, but not yet tested
+VisiblePosition endOfBlock(const VisiblePosition &c, EIncludeLineBreak includeLineBreak)
+{
+    Position p = c.deepEquivalent();
+
+    NodeImpl *startNode = p.node();
+    if (!startNode)
+        return VisiblePosition();
+
+    NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
+    NodeImpl *stayInsideBlock = includeLineBreak ? 0 : startBlock;
+    
+    NodeImpl *node = startNode;
+    long offset = p.offset();
+
+    for (NodeImpl *n = startNode; n; n = n->traverseNextNode(stayInsideBlock)) {
+        RenderObject *r = n->renderer();
+        if (!r)
+            continue;
+        RenderStyle *style = r->style();
+        if (style->visibility() != VISIBLE)
+            continue;
+        if (r->isBlockFlow()) {
+            if (includeLineBreak)
+                return VisiblePosition(n, 0);
+            break;
+        }
+        if (r->isText()) {
+            if (includeLineBreak && !n->isAncestor(startBlock))
+                return VisiblePosition(n, 0);
+            node = n;
+            offset = static_cast<RenderText *>(r)->length();
+        } else if (r->isReplaced()) {
+            node = n;
+            offset = 1;
+            if (includeLineBreak && !n->isAncestor(startBlock))
+                break;
+        }
+    }
+
+    return VisiblePosition(node, offset);
+}
+
+bool inSameBlock(const VisiblePosition &a, const VisiblePosition &b)
+{
+    return a.isNotNull() && startOfBlock(a) == startOfBlock(b);
+}
+
+bool isStartOfBlock(const VisiblePosition &pos)
+{
+    return pos.isNotNull() && pos == startOfBlock(pos);
+}
+
+bool isEndOfBlock(const VisiblePosition &pos)
+{
+    return pos.isNotNull() && pos == endOfBlock(pos, DoNotIncludeLineBreak);
+}
+
+// ---------
+
+VisiblePosition startOfDocument(const VisiblePosition &c)
+{
+    Position p = c.deepEquivalent();
+    NodeImpl *node = p.node();
+    if (!node)
+        return VisiblePosition();
+
+    DocumentImpl *doc = node->getDocument();
+    if (!doc)
+        return VisiblePosition();
+
+    return VisiblePosition(doc->documentElement(), 0);
+}
+
+VisiblePosition endOfDocument(const VisiblePosition &c)
+{
+    Position p = c.deepEquivalent();
+    NodeImpl *node = p.node();
+    if (!node)
+        return VisiblePosition();
+
+    DocumentImpl *doc = node->getDocument();
+    if (!doc)
+        return VisiblePosition();
+
+    NodeImpl *docElem = doc->documentElement();
+    if (!node)
+        return VisiblePosition();
+
+    return VisiblePosition(docElem, docElem->childNodeCount());
+}
+
+bool inSameDocument(const VisiblePosition &a, const VisiblePosition &b)
+{
+    Position ap = a.deepEquivalent();
+    NodeImpl *an = ap.node();
+    if (!an)
+        return false;
+    Position bp = b.deepEquivalent();
+    NodeImpl *bn = bp.node();
+    if (an == bn)
+        return true;
+
+    return an->getDocument() == bn->getDocument();
+}
+
+bool isStartOfDocument(const VisiblePosition &p)
+{
+    return p.isNotNull() && p.previous().isNull();
+}
+
+bool isEndOfDocument(const VisiblePosition &p)
+{
+    return p.isNotNull() && p.next().isNull();
+}
+
 } // namespace khtml
index 1ddf321595d1199d63cfe4ac9e7699ca64516655..0e4f97803b8b885ef82c3efd110591cf02356179 100644 (file)
@@ -46,14 +46,17 @@ VisiblePosition startOfLine(const VisiblePosition &, EAffinity);
 VisiblePosition endOfLine(const VisiblePosition &, EAffinity, EIncludeLineBreak = DoNotIncludeLineBreak);
 VisiblePosition previousLinePosition(const VisiblePosition &, EAffinity, int x);
 VisiblePosition nextLinePosition(const VisiblePosition &, EAffinity, int x);
+bool inSameLine(const VisiblePosition &, EAffinity, const VisiblePosition &, EAffinity);
+bool isStartOfLine(const VisiblePosition &, EAffinity);
+bool isEndOfLine(const VisiblePosition &, EAffinity);
 
 // sentences
 VisiblePosition startOfSentence(const VisiblePosition &);
-VisiblePosition endOfSentence(const VisiblePosition &, EIncludeLineBreak = DoNotIncludeLineBreak);
+VisiblePosition endOfSentence(const VisiblePosition &);
 VisiblePosition previousSentencePosition(const VisiblePosition &, EAffinity, int x);
 VisiblePosition nextSentencePosition(const VisiblePosition &, EAffinity, int x);
 
-// paragraphs
+// paragraphs (perhaps a misnomer, can be divided by line break elements)
 VisiblePosition startOfParagraph(const VisiblePosition &);
 VisiblePosition endOfParagraph(const VisiblePosition &, EIncludeLineBreak = DoNotIncludeLineBreak);
 VisiblePosition previousParagraphPosition(const VisiblePosition &, EAffinity, int x);
@@ -62,6 +65,20 @@ bool inSameParagraph(const VisiblePosition &, const VisiblePosition &);
 bool isStartOfParagraph(const VisiblePosition &);
 bool isEndOfParagraph(const VisiblePosition &);
 
+// blocks (true paragraphs; line break elements don't break blocks)
+VisiblePosition startOfBlock(const VisiblePosition &);
+VisiblePosition endOfBlock(const VisiblePosition &, EIncludeLineBreak = DoNotIncludeLineBreak);
+bool inSameBlock(const VisiblePosition &, const VisiblePosition &);
+bool isStartOfBlock(const VisiblePosition &);
+bool isEndOfBlock(const VisiblePosition &);
+
+// document
+VisiblePosition startOfDocument(const VisiblePosition &);
+VisiblePosition endOfDocument(const VisiblePosition &);
+bool inSameDocument(const VisiblePosition &, const VisiblePosition &);
+bool isStartOfDocument(const VisiblePosition &);
+bool isEndOfDocument(const VisiblePosition &);
+
 } // namespace DOM
 
 #endif // KHTML_EDITING_VISIBLE_POSITION_H