Reviewed by Darin
authorkocienda <kocienda@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Aug 2004 17:27:42 +0000 (17:27 +0000)
committerkocienda <kocienda@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Aug 2004 17:27:42 +0000 (17:27 +0000)
        Fix for this bug:
        <rdar://problem/3675812> Moving a word at a time does not use the correct conception of "word"

        I have implemented versions of previousWordPosition and nextWordPosition that are now
        different than previousWordBoundary and nextWordBoundary. The behavior of the new
        functions attempts to match what Cocoa does as closely as it can. Let the bug filing begin!

        * WebCore.pbproj/project.pbxproj: Added KWQTextUtilities.mm
        * khtml/misc/helper.cpp:
        (khtml::nextWordFromIndex): Glue to call through to KWQFindNextWordFromIndex.
        * khtml/misc/helper.h: Declare the function above.
        * khtml/misc/khtml_text_operations.cpp: Added SimplifiedBackwardsTextIterator class.
        (khtml::SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator): New
        (khtml::SimplifiedBackwardsTextIterator::advance): Ditto.
        (khtml::SimplifiedBackwardsTextIterator::handleTextNode): Ditto.
        (khtml::SimplifiedBackwardsTextIterator::handleReplacedElement): Ditto.
        (khtml::SimplifiedBackwardsTextIterator::handleNonTextNode): Ditto.
        (khtml::SimplifiedBackwardsTextIterator::exitNode): Ditto.
        (khtml::SimplifiedBackwardsTextIterator::emitCharacter): Ditto.
        (khtml::SimplifiedBackwardsTextIterator::range): Ditto.
        * khtml/misc/khtml_text_operations.h:
        (khtml::SimplifiedBackwardsTextIterator::atEnd): Ditto.
        (khtml::SimplifiedBackwardsTextIterator::length): Ditto.
        (khtml::SimplifiedBackwardsTextIterator::characters): Ditto.
        * khtml/xml/dom_position.cpp:
        (DOM::Position::previousWordBoundary): Updated to gather appropriate text and call through to
        AppKit to perform the same calculations NSText uses.
        (DOM::Position::nextWordBoundary): Ditto.
        (DOM::Position::previousWordPosition): Unrelated change to fix case where the function could get "stuck".
        (DOM::Position::nextWordPosition): Ditto
        (DOM::Position::equivalentDeepPosition): Changed to look backwards if the position's offset is equal
        to the number of child nodes it has. This handles more cases correctly, like when the position is
        gives as one beyond the end of a document element's last child.
        * kwq/KWQTextUtilities.h: Declared KWQFindNextWordFromIndex.
        * kwq/KWQTextUtilities.mm: Added.
        (KWQFindNextWordFromIndex): New function.

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

WebCore/ChangeLog-2005-08-23
WebCore/WebCore.pbproj/project.pbxproj
WebCore/khtml/editing/visible_text.cpp
WebCore/khtml/editing/visible_text.h
WebCore/khtml/misc/helper.cpp
WebCore/khtml/misc/helper.h
WebCore/khtml/misc/khtml_text_operations.cpp
WebCore/khtml/misc/khtml_text_operations.h
WebCore/khtml/xml/dom_position.cpp
WebCore/kwq/KWQTextUtilities.h

index d9ab5b00372905b2aef8a7ab58116b86d15ca7dd..b85fdea0ebff69cf95833650e31ff740e28be020 100644 (file)
@@ -1,3 +1,44 @@
+2004-08-11  Ken Kocienda  <kocienda@apple.com>
+
+        Reviewed by Darin
+
+        Fix for this bug:
+        <rdar://problem/3675812> Moving a word at a time does not use the correct conception of "word"
+        
+        I have implemented versions of previousWordPosition and nextWordPosition that are now
+        different than previousWordBoundary and nextWordBoundary. The behavior of the new
+        functions attempts to match what Cocoa does as closely as it can. Let the bug filing begin! 
+
+        * WebCore.pbproj/project.pbxproj: Added KWQTextUtilities.mm
+        * khtml/misc/helper.cpp:
+        (khtml::nextWordFromIndex): Glue to call through to KWQFindNextWordFromIndex.
+        * khtml/misc/helper.h: Declare the function above.
+        * khtml/misc/khtml_text_operations.cpp: Added SimplifiedBackwardsTextIterator class.
+        (khtml::SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator): New
+        (khtml::SimplifiedBackwardsTextIterator::advance): Ditto.
+        (khtml::SimplifiedBackwardsTextIterator::handleTextNode): Ditto.
+        (khtml::SimplifiedBackwardsTextIterator::handleReplacedElement): Ditto.
+        (khtml::SimplifiedBackwardsTextIterator::handleNonTextNode): Ditto.
+        (khtml::SimplifiedBackwardsTextIterator::exitNode): Ditto.
+        (khtml::SimplifiedBackwardsTextIterator::emitCharacter): Ditto.
+        (khtml::SimplifiedBackwardsTextIterator::range): Ditto.
+        * khtml/misc/khtml_text_operations.h:
+        (khtml::SimplifiedBackwardsTextIterator::atEnd): Ditto.
+        (khtml::SimplifiedBackwardsTextIterator::length): Ditto.
+        (khtml::SimplifiedBackwardsTextIterator::characters): Ditto.
+        * khtml/xml/dom_position.cpp:
+        (DOM::Position::previousWordBoundary): Updated to gather appropriate text and call through to
+        AppKit to perform the same calculations NSText uses.
+        (DOM::Position::nextWordBoundary): Ditto.
+        (DOM::Position::previousWordPosition): Unrelated change to fix case where the function could get "stuck".
+        (DOM::Position::nextWordPosition): Ditto
+        (DOM::Position::equivalentDeepPosition): Changed to look backwards if the position's offset is equal
+        to the number of child nodes it has. This handles more cases correctly, like when the position is
+        gives as one beyond the end of a document element's last child.
+        * kwq/KWQTextUtilities.h: Declared KWQFindNextWordFromIndex.
+        * kwq/KWQTextUtilities.mm: Added.
+        (KWQFindNextWordFromIndex): New function.
+
 2004-08-11  Ken Kocienda  <kocienda@apple.com>
 
         Reviewed by John
index 93507bbc7da1e27bf30397484ff6a471435c2732..deb0f87b9be8af16960c543fefd136812480d437 100644 (file)
                                BE02F485066E1C550013A9F6,
                                BE02D4E9066F908A0076809F,
                                BC9D580606C185A4001E893C,
+                               BE1A407206CA8A33005B28CF,
                        );
                        isa = PBXSourcesBuildPhase;
                        runOnlyForDeploymentPostprocessing = 0;
                        settings = {
                        };
                };
+               BE1A407106CA8A33005B28CF = {
+                       fileEncoding = 30;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.cpp.objcpp;
+                       path = KWQTextUtilities.mm;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               BE1A407206CA8A33005B28CF = {
+                       fileRef = BE1A407106CA8A33005B28CF;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
                BE26F15305517DE000BFA0C3 = {
                        fileEncoding = 30;
                        isa = PBXFileReference;
                                F587852F02DE375901EA4122,
                                BE8BD8F206359F6000D3F20B,
                                BE8BD8F306359F6000D3F20B,
+                               BE1A407106CA8A33005B28CF,
                                F587853502DE375901EA4122,
                                F587853602DE375901EA4122,
                                F587851B02DE375901EA4122,
index 5fe98bd726e16a8c68cda0861a90f5e7cf022a6e..260b3ca54bc4b53fde9153a08868353a1a979c67 100644 (file)
@@ -451,6 +451,210 @@ Range TextIterator::range() const
     }
 }
 
+SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator() : m_positionNode(0)
+{
+}
+
+SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range &r)
+{
+    if (r.isNull()) {
+        m_positionNode = 0;
+        return;
+    }
+
+    NodeImpl *startNode = r.startContainer().handle();
+    NodeImpl *endNode = r.endContainer().handle();
+    long startOffset = r.startOffset();
+    long endOffset = r.endOffset();
+
+    if (!offsetInCharacters(startNode->nodeType())) {
+        if (startOffset >= 0 && startOffset < static_cast<long>(startNode->childNodeCount())) {
+            startNode = startNode->childNode(startOffset);
+            startOffset = 0;
+        }
+    }
+    if (!offsetInCharacters(endNode->nodeType())) {
+        if (endOffset > 0 && endOffset <= static_cast<long>(endNode->childNodeCount())) {
+            endNode = endNode->childNode(endOffset - 1);
+            endOffset = endNode->hasChildNodes() ? endNode->childNodeCount() : endNode->maxOffset();
+        }
+    }
+
+    m_node = endNode;
+    m_offset = endOffset;
+    m_handledNode = false;
+    m_handledChildren = false;
+
+    m_startNode = startNode;
+    m_startOffset = startOffset;
+
+#ifndef NDEBUG
+    // Need this just because of the assert.
+    m_positionNode = endNode;
+#endif
+
+    advance();
+}
+
+void SimplifiedBackwardsTextIterator::advance()
+{
+    assert(m_positionNode);
+
+    m_positionNode = 0;
+    m_textLength = 0;
+
+    while (m_node) {
+        if (!m_handledNode) {
+            RenderObject *renderer = m_node->renderer();
+            if (renderer && renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) {
+                // FIXME: What about CDATA_SECTION_NODE?
+                if (renderer->style()->visibility() == VISIBLE && m_offset > 0) {
+                    m_handledNode = handleTextNode();
+                }
+            } else if (renderer && (renderer->isImage() || renderer->isWidget())) {
+                if (renderer->style()->visibility() == VISIBLE && m_offset > 0) {
+                    m_handledNode = handleReplacedElement();
+                }
+            } else {
+                m_handledNode = handleNonTextNode();
+            }
+            if (m_positionNode) {
+                return;
+            }
+        }
+
+        if (m_node == m_startNode)
+            return;
+
+        NodeImpl *next = 0;
+        if (!m_handledChildren) {
+            next = m_node->lastChild();
+            while (next && next->lastChild())
+                next = next->lastChild();
+            m_handledChildren = true;
+        }
+        if (!next && m_node != m_startNode) {
+            next = m_node->previousSibling();
+            if (next) {
+                exitNode();
+                while (next->lastChild())
+                    next = next->lastChild();
+            }
+            else if (m_node->parentNode()) {
+                next = m_node->parentNode();
+                exitNode();
+            }
+        }
+        
+        m_node = next;
+        if (m_node)
+            m_offset = m_node->caretMaxOffset();
+        else
+            m_offset = 0;
+        m_handledNode = false;
+        
+        if (m_positionNode) {
+            return;
+        }
+    }
+}
+
+bool SimplifiedBackwardsTextIterator::handleTextNode()
+{
+    RenderText *renderer = static_cast<RenderText *>(m_node->renderer());
+    DOMString str = m_node->nodeValue();
+
+    if (!renderer->firstTextBox() && str.length() > 0) {
+        return true;
+    }
+
+    m_positionEndOffset = m_offset;
+
+    m_offset = (m_node == m_startNode) ? m_startOffset : 0;
+    m_positionNode = m_node;
+    m_positionStartOffset = m_offset;
+    m_textLength = m_positionEndOffset - m_positionStartOffset;
+    m_textCharacters = str.unicode() + m_positionStartOffset;
+
+    return true;
+}
+
+bool SimplifiedBackwardsTextIterator::handleReplacedElement()
+{
+    long offset = m_node->nodeIndex();
+
+    m_positionNode = m_node->parentNode();
+    m_positionStartOffset = offset;
+    m_positionEndOffset = offset + 1;
+
+    m_textCharacters = 0;
+    m_textLength = 0;
+
+    return true;
+}
+
+bool SimplifiedBackwardsTextIterator::handleNonTextNode()
+{
+    switch (m_node->id()) {
+        case ID_BR:
+        case ID_TD:
+        case ID_TH:
+        case ID_BLOCKQUOTE:
+        case ID_DD:
+        case ID_DIV:
+        case ID_DL:
+        case ID_DT:
+        case ID_H1:
+        case ID_H2:
+        case ID_H3:
+        case ID_H4:
+        case ID_H5:
+        case ID_H6:
+        case ID_HR:
+        case ID_LI:
+        case ID_OL:
+        case ID_P:
+        case ID_PRE:
+        case ID_TR:
+        case ID_UL:
+            // Emit a space to "break up" content. Any word break
+            // character will do.
+            emitCharacter(' ', m_node, 0, 0);
+            break;
+    }
+
+    return true;
+}
+
+void SimplifiedBackwardsTextIterator::exitNode()
+{
+    // Left this function in so that the name and usage patters remain similar to 
+    // TextIterator. However, the needs of this iterator are much simpler, and
+    // the handleNonTextNode() function does just what we want (i.e. insert a
+    // space for certain node types to "break up" text so that it does not seem
+    // like content is next to other text when it really isn't). 
+    handleNonTextNode();
+}
+
+void SimplifiedBackwardsTextIterator::emitCharacter(QChar c, NodeImpl *node, long startOffset, long endOffset)
+{
+    m_singleCharacterBuffer = c;
+    m_positionNode = node;
+    m_positionStartOffset = startOffset;
+    m_positionEndOffset = endOffset;
+    m_textCharacters = &m_singleCharacterBuffer;
+    m_textLength = 1;
+}
+
+Range SimplifiedBackwardsTextIterator::range() const
+{
+    if (m_positionNode) {
+        return Range(m_positionNode, m_positionStartOffset, m_positionNode, m_positionEndOffset);
+    } else {
+        return Range(m_startNode, m_startOffset, m_startNode, m_startOffset);
+    }
+}
+
 CharacterIterator::CharacterIterator()
     : m_offset(0), m_runOffset(0), m_atBreak(true)
 {
index d3b5b3f8a89696f9d4fe9233fce9f9931306739f..475fedc80bb56e7600311b039d7082abe26a4f29 100644 (file)
@@ -105,6 +105,51 @@ private:
     QChar m_singleCharacterBuffer;
 };
 
+// Iterates through the DOM range, returning all the text, and 0-length boundaries
+// at points where replaced elements break up the text flow.  The text comes back in
+// chunks so as to optimize for performance of the iteration.
+class SimplifiedBackwardsTextIterator
+{
+public:
+    SimplifiedBackwardsTextIterator();
+    explicit SimplifiedBackwardsTextIterator(const DOM::Range &);
+    
+    bool atEnd() const { return !m_positionNode; }
+    void advance();
+    
+    long length() const { return m_textLength; }
+    const QChar *characters() const { return m_textCharacters; }
+    
+    DOM::Range range() const;
+        
+private:
+    void exitNode();
+    bool handleTextNode();
+    bool handleReplacedElement();
+    bool handleNonTextNode();
+    void emitCharacter(QChar, DOM::NodeImpl *Node, long startOffset, long endOffset);
+    
+    // Current position, not necessarily of the text being returned, but position
+    // as we walk through the DOM tree.
+    DOM::NodeImpl *m_node;
+    long m_offset;
+    bool m_handledNode;
+    bool m_handledChildren;
+    
+    // End of the range.
+    DOM::NodeImpl *m_startNode;
+    long m_startOffset;
+    
+    // The current text and its position, in the form to be returned from the iterator.
+    DOM::NodeImpl *m_positionNode;
+    long m_positionStartOffset;
+    long m_positionEndOffset;
+    const QChar *m_textCharacters;
+    long m_textLength;
+    
+    // Used for whitespace characters that aren't in the DOM, so we can point at them.
+    QChar m_singleCharacterBuffer;
+};
 
 // Builds on the text iterator, adding a character position so we can walk one
 // character at a time, or faster, as needed. Useful for searching.
index 40bdda914751155f2e2253cb3b42f7b808f866ac..50b83d79b143910b062f51f7cccb53fdc89572bb 100644 (file)
@@ -45,4 +45,13 @@ void findWordBoundary(QChar *chars, int len, int position, int *start, int *end)
 #endif
 }
 
+int nextWordFromIndex(QChar *chars, int len, int position, bool forward)
+{
+#if APPLE_CHANGES
+    return KWQFindNextWordFromIndex(chars, len, position, forward);
+#else
+    // KDE implementation
+#endif
+}
+
 }
index 7875aaad15bb2046bb831a4ba0c09d71643c34dd..88631f09738345c9b08f272add14b5cd7a3ca69c 100644 (file)
@@ -40,6 +40,7 @@ namespace khtml
     void setPrintPainter( QPainter *printer );
 
     void findWordBoundary(QChar *chars, int len, int position, int *start, int *end);
+    int nextWordFromIndex(QChar *chars, int len, int position, bool forward);
 };
 
 #endif
index 5fe98bd726e16a8c68cda0861a90f5e7cf022a6e..260b3ca54bc4b53fde9153a08868353a1a979c67 100644 (file)
@@ -451,6 +451,210 @@ Range TextIterator::range() const
     }
 }
 
+SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator() : m_positionNode(0)
+{
+}
+
+SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range &r)
+{
+    if (r.isNull()) {
+        m_positionNode = 0;
+        return;
+    }
+
+    NodeImpl *startNode = r.startContainer().handle();
+    NodeImpl *endNode = r.endContainer().handle();
+    long startOffset = r.startOffset();
+    long endOffset = r.endOffset();
+
+    if (!offsetInCharacters(startNode->nodeType())) {
+        if (startOffset >= 0 && startOffset < static_cast<long>(startNode->childNodeCount())) {
+            startNode = startNode->childNode(startOffset);
+            startOffset = 0;
+        }
+    }
+    if (!offsetInCharacters(endNode->nodeType())) {
+        if (endOffset > 0 && endOffset <= static_cast<long>(endNode->childNodeCount())) {
+            endNode = endNode->childNode(endOffset - 1);
+            endOffset = endNode->hasChildNodes() ? endNode->childNodeCount() : endNode->maxOffset();
+        }
+    }
+
+    m_node = endNode;
+    m_offset = endOffset;
+    m_handledNode = false;
+    m_handledChildren = false;
+
+    m_startNode = startNode;
+    m_startOffset = startOffset;
+
+#ifndef NDEBUG
+    // Need this just because of the assert.
+    m_positionNode = endNode;
+#endif
+
+    advance();
+}
+
+void SimplifiedBackwardsTextIterator::advance()
+{
+    assert(m_positionNode);
+
+    m_positionNode = 0;
+    m_textLength = 0;
+
+    while (m_node) {
+        if (!m_handledNode) {
+            RenderObject *renderer = m_node->renderer();
+            if (renderer && renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) {
+                // FIXME: What about CDATA_SECTION_NODE?
+                if (renderer->style()->visibility() == VISIBLE && m_offset > 0) {
+                    m_handledNode = handleTextNode();
+                }
+            } else if (renderer && (renderer->isImage() || renderer->isWidget())) {
+                if (renderer->style()->visibility() == VISIBLE && m_offset > 0) {
+                    m_handledNode = handleReplacedElement();
+                }
+            } else {
+                m_handledNode = handleNonTextNode();
+            }
+            if (m_positionNode) {
+                return;
+            }
+        }
+
+        if (m_node == m_startNode)
+            return;
+
+        NodeImpl *next = 0;
+        if (!m_handledChildren) {
+            next = m_node->lastChild();
+            while (next && next->lastChild())
+                next = next->lastChild();
+            m_handledChildren = true;
+        }
+        if (!next && m_node != m_startNode) {
+            next = m_node->previousSibling();
+            if (next) {
+                exitNode();
+                while (next->lastChild())
+                    next = next->lastChild();
+            }
+            else if (m_node->parentNode()) {
+                next = m_node->parentNode();
+                exitNode();
+            }
+        }
+        
+        m_node = next;
+        if (m_node)
+            m_offset = m_node->caretMaxOffset();
+        else
+            m_offset = 0;
+        m_handledNode = false;
+        
+        if (m_positionNode) {
+            return;
+        }
+    }
+}
+
+bool SimplifiedBackwardsTextIterator::handleTextNode()
+{
+    RenderText *renderer = static_cast<RenderText *>(m_node->renderer());
+    DOMString str = m_node->nodeValue();
+
+    if (!renderer->firstTextBox() && str.length() > 0) {
+        return true;
+    }
+
+    m_positionEndOffset = m_offset;
+
+    m_offset = (m_node == m_startNode) ? m_startOffset : 0;
+    m_positionNode = m_node;
+    m_positionStartOffset = m_offset;
+    m_textLength = m_positionEndOffset - m_positionStartOffset;
+    m_textCharacters = str.unicode() + m_positionStartOffset;
+
+    return true;
+}
+
+bool SimplifiedBackwardsTextIterator::handleReplacedElement()
+{
+    long offset = m_node->nodeIndex();
+
+    m_positionNode = m_node->parentNode();
+    m_positionStartOffset = offset;
+    m_positionEndOffset = offset + 1;
+
+    m_textCharacters = 0;
+    m_textLength = 0;
+
+    return true;
+}
+
+bool SimplifiedBackwardsTextIterator::handleNonTextNode()
+{
+    switch (m_node->id()) {
+        case ID_BR:
+        case ID_TD:
+        case ID_TH:
+        case ID_BLOCKQUOTE:
+        case ID_DD:
+        case ID_DIV:
+        case ID_DL:
+        case ID_DT:
+        case ID_H1:
+        case ID_H2:
+        case ID_H3:
+        case ID_H4:
+        case ID_H5:
+        case ID_H6:
+        case ID_HR:
+        case ID_LI:
+        case ID_OL:
+        case ID_P:
+        case ID_PRE:
+        case ID_TR:
+        case ID_UL:
+            // Emit a space to "break up" content. Any word break
+            // character will do.
+            emitCharacter(' ', m_node, 0, 0);
+            break;
+    }
+
+    return true;
+}
+
+void SimplifiedBackwardsTextIterator::exitNode()
+{
+    // Left this function in so that the name and usage patters remain similar to 
+    // TextIterator. However, the needs of this iterator are much simpler, and
+    // the handleNonTextNode() function does just what we want (i.e. insert a
+    // space for certain node types to "break up" text so that it does not seem
+    // like content is next to other text when it really isn't). 
+    handleNonTextNode();
+}
+
+void SimplifiedBackwardsTextIterator::emitCharacter(QChar c, NodeImpl *node, long startOffset, long endOffset)
+{
+    m_singleCharacterBuffer = c;
+    m_positionNode = node;
+    m_positionStartOffset = startOffset;
+    m_positionEndOffset = endOffset;
+    m_textCharacters = &m_singleCharacterBuffer;
+    m_textLength = 1;
+}
+
+Range SimplifiedBackwardsTextIterator::range() const
+{
+    if (m_positionNode) {
+        return Range(m_positionNode, m_positionStartOffset, m_positionNode, m_positionEndOffset);
+    } else {
+        return Range(m_startNode, m_startOffset, m_startNode, m_startOffset);
+    }
+}
+
 CharacterIterator::CharacterIterator()
     : m_offset(0), m_runOffset(0), m_atBreak(true)
 {
index d3b5b3f8a89696f9d4fe9233fce9f9931306739f..475fedc80bb56e7600311b039d7082abe26a4f29 100644 (file)
@@ -105,6 +105,51 @@ private:
     QChar m_singleCharacterBuffer;
 };
 
+// Iterates through the DOM range, returning all the text, and 0-length boundaries
+// at points where replaced elements break up the text flow.  The text comes back in
+// chunks so as to optimize for performance of the iteration.
+class SimplifiedBackwardsTextIterator
+{
+public:
+    SimplifiedBackwardsTextIterator();
+    explicit SimplifiedBackwardsTextIterator(const DOM::Range &);
+    
+    bool atEnd() const { return !m_positionNode; }
+    void advance();
+    
+    long length() const { return m_textLength; }
+    const QChar *characters() const { return m_textCharacters; }
+    
+    DOM::Range range() const;
+        
+private:
+    void exitNode();
+    bool handleTextNode();
+    bool handleReplacedElement();
+    bool handleNonTextNode();
+    void emitCharacter(QChar, DOM::NodeImpl *Node, long startOffset, long endOffset);
+    
+    // Current position, not necessarily of the text being returned, but position
+    // as we walk through the DOM tree.
+    DOM::NodeImpl *m_node;
+    long m_offset;
+    bool m_handledNode;
+    bool m_handledChildren;
+    
+    // End of the range.
+    DOM::NodeImpl *m_startNode;
+    long m_startOffset;
+    
+    // The current text and its position, in the form to be returned from the iterator.
+    DOM::NodeImpl *m_positionNode;
+    long m_positionStartOffset;
+    long m_positionEndOffset;
+    const QChar *m_textCharacters;
+    long m_textLength;
+    
+    // Used for whitespace characters that aren't in the DOM, so we can point at them.
+    QChar m_singleCharacterBuffer;
+};
 
 // Builds on the text iterator, adding a character position so we can walk one
 // character at a time, or faster, as needed. Useful for searching.
index 432f086b07c299a0019bb759b23f556abbb74412..52f194a372ed31f16fd3625572cc9f39b42f3ecf 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "helper.h"
 #include "htmltags.h"
+#include "khtml_text_operations.h"
 #include "qstring.h"
 #include "rendering/render_block.h"
 #include "rendering/render_line.h"
@@ -45,6 +46,7 @@
 #define LOG(channel, formatAndArgs...) ((void)0)
 #endif
 
+using khtml::CharacterIterator;
 using khtml::InlineBox;
 using khtml::InlineFlowBox;
 using khtml::InlineTextBox;
@@ -52,6 +54,8 @@ using khtml::RenderBlock;
 using khtml::RenderObject;
 using khtml::RenderText;
 using khtml::RootInlineBox;
+using khtml::SimplifiedBackwardsTextIterator;
+using khtml::TextIterator;
 
 namespace DOM {
 
@@ -299,12 +303,16 @@ Position Position::previousWordBoundary() const
             int start, end;
             khtml::findWordBoundary(chars, len, pos.offset(), &start, &end);
             pos = Position(pos.node(), start);
+            if (pos != *this)
+                return pos;
+            else
+                pos = pos.previousCharacterPosition();
         }
         else {
             pos = Position(pos.node(), pos.node()->caretMinOffset());
+            if (pos != *this)
+                return pos;
         }
-        if (pos != *this)
-            return pos;
         tries++;
     }
     
@@ -326,12 +334,16 @@ Position Position::nextWordBoundary() const
             int start, end;
             khtml::findWordBoundary(chars, len, pos.offset(), &start, &end);
             pos = Position(pos.node(), end);
+            if (pos != *this)
+                return pos;
+            else
+                pos = pos.nextCharacterPosition();
         }
         else {
             pos = Position(pos.node(), pos.node()->caretMaxOffset());
+            if (pos != *this)
+                return pos;
         }
-        if (pos != *this)
-            return pos;
         tries++;
     }
     
@@ -340,14 +352,113 @@ Position Position::nextWordBoundary() const
 
 Position Position::previousWordPosition() const
 {
-    // FIXME - need an implementation that skips to starts of words, not each boundary
-    return previousWordBoundary();
+    if (isEmpty())
+        return Position();
+
+    Range searchRange(node()->getDocument());
+    searchRange.setStartBefore(node()->getDocument()->documentElement());
+    Position end(equivalentRangeCompliantPosition());
+    searchRange.setEnd(end.node(), end.offset());
+    SimplifiedBackwardsTextIterator it(searchRange);
+    QString string;
+    unsigned next = 0;
+    while (!it.atEnd() && it.length() > 0) {
+        // Keep asking the iterator for chunks until the nextWordFromIndex() function
+        // returns a non-zero value.
+        string.prepend(QString(it.characters(), it.length()));
+        next = khtml::nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), string.length(), false);
+        if (next != 0)
+            break;
+        it.advance();
+    }
+    
+    Position pos(*this);
+    if (it.atEnd() && next == 0) {
+        Range range(it.range());
+        pos = Position(range.startContainer().handle(), range.startOffset());
+    }
+    else if (!it.atEnd() && it.length() == 0) {
+        // Got a zero-length chunk.
+        // This means we have hit a replaced element.
+        // Make a check to see if the position should be before or after the replaced element
+        // by performing an additional check with a modified string which uses an "X" 
+        // character to stand in for the replaced element.
+        string.prepend("X ");
+        unsigned pastImage = khtml::nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), string.length(), false);
+        Range range(it.range());
+        if (pastImage == 0)
+            pos = Position(range.startContainer().handle(), range.startOffset());
+        else
+            pos = Position(range.endContainer().handle(), range.endOffset());
+    }
+    else if (next != 0) {
+        // The simpler iterator used in this function, as compared to the one used in 
+        // nextWordPosition(), gives us results we can use directly without having to 
+        // iterate again to translate the next value into a DOM position. 
+        NodeImpl *node = it.range().startContainer().handle();
+        if (node->isTextNode()) {
+            // The next variable contains a usable index into a text node
+            pos = Position(node, next);
+        }
+        else {
+            // If we are not in a text node, we ended on a node boundary, so the
+            // range start offset should be used.
+            pos = Position(node, it.range().startOffset());
+        }
+    }
+    pos = pos.equivalentDeepPosition().closestRenderedPosition(UPSTREAM);
+    return pos;
 }
 
 Position Position::nextWordPosition() const
 {
-    // FIXME - need an implementation that skips to ends of words, not each boundary
-    return nextWordBoundary();
+    if (isEmpty())
+        return Position();
+
+    Range searchRange(node()->getDocument());
+    Position start(equivalentRangeCompliantPosition());
+    searchRange.setStart(start.node(), start.offset());
+    searchRange.setEndAfter(node()->getDocument()->documentElement());
+    TextIterator it(searchRange);
+    QString string;
+    unsigned next = 0;
+    while (!it.atEnd() && it.length() > 0) {
+        // Keep asking the iterator for chunks until the nextWordFromIndex() function
+        // returns a value not equal to the length of the string passed to it.
+        string += QString(it.characters(), it.length());
+        next = khtml::nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), 0, true);
+        if (next != string.length())
+            break;
+        it.advance();
+    }
+    
+    Position pos(*this);
+    if (it.atEnd() && next == string.length()) {
+        Range range(it.range());
+        pos = Position(range.startContainer().handle(), range.startOffset());
+    }
+    else if (!it.atEnd() && it.length() == 0) {
+        // Got a zero-length chunk.
+        // This means we have hit a replaced element.
+        // Make a check to see if the position should be before or after the replaced element
+        // by performing an additional check with a modified string which uses an "X" 
+        // character to stand in for the replaced element.
+        string += " X";
+        unsigned pastImage = khtml::nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), 0, true);
+        Range range(it.range());
+        if (next != pastImage)
+            pos = Position(range.endContainer().handle(), range.endOffset());
+        else
+            pos = Position(range.startContainer().handle(), range.startOffset());
+    }
+    else if (next != 0) {
+        // Use the character iterator to translate the next value into a DOM position.
+        CharacterIterator charIt(searchRange);
+        charIt.advance(next - 1);
+        pos = Position(charIt.range().endContainer().handle(), charIt.range().endOffset());
+    }
+    pos = pos.equivalentDeepPosition().closestRenderedPosition(UPSTREAM);
+    return pos;
 }
 
 Position Position::previousLinePosition(int x) const
@@ -602,16 +713,26 @@ Position Position::equivalentDeepPosition() const
         return *this;
 
     NodeImpl *child = 0;
-    if (offset() >= (int)node()->childNodeCount())
+    Position pos(*this);
+    if (offset() >= (int)node()->childNodeCount()) {
         child = node()->lastChild();
-    else
+        pos = Position(child, child->caretMaxOffset());
+        ASSERT(child);
+        while (!child->isAtomicNode() && pos.node()->hasChildNodes()) {
+            child = pos.node()->lastChild();
+            ASSERT(child);
+            pos = Position(child, child->caretMaxOffset());
+        }
+    }
+    else {
         child = node()->childNode(offset());
-    ASSERT(child);
-    Position pos(child, 0);
-    while (!child->isAtomicNode() && pos.node()->hasChildNodes()) {
-        child = pos.node()->firstChild();
         ASSERT(child);
         pos = Position(child, 0);
+        while (!child->isAtomicNode() && pos.node()->hasChildNodes()) {
+            child = pos.node()->firstChild();
+            ASSERT(child);
+            pos = Position(child, 0);
+        }
     }
     return pos;
 }
index bedeb321413ef394b171bc69951a13a7a9dddacc..1bd36cb212c2921ef55e7a785034ba1b4288def8 100644 (file)
@@ -29,5 +29,6 @@
 class QChar;
  
 void KWQFindWordBoundary(QChar *chars, int len, int position, int *start, int *end);
+int KWQFindNextWordFromIndex(QChar *chars, int len, int position, bool forward);
 
 #endif
\ No newline at end of file