[SCM] WebKit Debian packaging branch, debian/unstable, updated. debian/1.1.15-1-40151-g37bb677

trey trey at 268f45cc-cd09-0410-ab3c-d52691b4dbfc
Sat Sep 26 08:53:06 UTC 2009


The following commit has been merged in the debian/unstable branch:
commit 5d1e97410dff771f007164fd5d77785e85d24585
Author: trey <trey at 268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Date:   Wed Jul 28 21:24:47 2004 +0000

    Tests:
    
    	Cut out unimplemented spelling methods.  WebKit implements them.
    
            Reviewed by Ken
    
            * Blot/BlotDocument.h:
            * Blot/BlotDocument.m:
    
    WebCore:
    
    	Spellchecking, Part I.  Basic spellcheck is working.  Spelling panel is hooked up.
    
    	At this point, no special marking of misspellings, no grammar check, no context
    	menu integration, no "check continually" mode.
    
    	Much of the TextIterator and CharacterIterator interface got published outside
    	of khtml_text_operations.cpp, with a little API rationalizing.
    
            Reviewed by Ken.
    
            * khtml/misc/khtml_text_operations.cpp:
            (khtml::TextIterator::range):  Name changes.
            (khtml::CharacterIterator::CharacterIterator):  Ditto.
            (khtml::CharacterIterator::range):  Ditto.
            (khtml::CharacterIterator::advance):  Ditto.
            (khtml::CharacterIterator::string):  New method to consume chars into a string.
            (khtml::WordAwareIterator::WordAwareIterator):  New class that iterates over
    	the text respecting word boundaries.
            (khtml::WordAwareIterator::advance):
            (khtml::WordAwareIterator::length):
            (khtml::WordAwareIterator::characters):
            (khtml::plainText):  Name changes.
            (khtml::findPlainText):  Ditto.
    
    	API moved from cpp to header file.
            * khtml/misc/khtml_text_operations.h:
            (khtml::TextIterator::atEnd):
            (khtml::TextIterator::length):
            (khtml::TextIterator::characters):
            (khtml::CharacterIterator::atBreak):
            (khtml::CharacterIterator::atEnd):
            (khtml::CharacterIterator::length):
            (khtml::CharacterIterator::characters):
            (khtml::CharacterIterator::characterOffset):
            (khtml::WordAwareIterator::atEnd):
            (khtml::WordAwareIterator::range):
    
            * khtml/xml/dom_position.cpp:
            (DOM::Position::previousWordBoundary):  New name for the old routine.  This routines semantics
    	match the current behavior of this code.
            (DOM::Position::nextWordBoundary):  Ditto.
            (DOM::Position::previousWordPosition):  Call old code with the new name.  When we fix
    	word advancement, this routine will have its own impl.
            (DOM::Position::nextWordPosition):  Ditto.
            * khtml/xml/dom_position.h:
            * khtml/xml/dom_selection.h:
            (DOM::Selection::rangeStart):  New convenience methods
            (DOM::Selection::rangeEnd):
            * kwq/KWQKHTMLPart.h:
            * kwq/KWQKHTMLPart.mm:
            (KWQKHTMLPart::findString):  Ensure we use range-compatible positions.
            (KWQKHTMLPart::advanceToNextMisspelling):  Brand new.
            * kwq/WebCoreBridge.h:
            * kwq/WebCoreBridge.mm:
            (-[WebCoreBridge advanceToNextMisspelling]):  Typical bridge glue.
    
    WebKit:
    
    	Spellchecking, Part I.  Basic spellcheck is working.  Spelling panel is hooked up.
    
    	At this point, no special marking of misspellings, no grammar check, no context
    	menu integration, no "check continually" mode.
    
            Reviewed by Ken.
    
            * WebCoreSupport.subproj/WebBridge.m:
            (-[WebBridge spellCheckerDocumentTag]):  Typical bridge glue.
            * WebView.subproj/WebHTMLView.m:
            (-[WebHTMLView validateUserInterfaceItem:]):  Validate various spelling actions.
            (-[WebHTMLView checkSpelling:]):  Call WC for real work, update panel.
            (-[WebHTMLView showGuessPanel:]):  Show panel, call WC for real work.
            (-[WebHTMLView _changeSpellingToWord:]):  Apply correction to our doc.
            (-[WebHTMLView changeSpelling:]):  Simple pass through to above method.
            (-[WebHTMLView ignoreSpelling:]):  Tell checker to ignore the word.
            * WebView.subproj/WebView.m:
            (-[WebView _close]):  Call AK's closeSpellDocumentWithTag: for proper cleanup.
    
    
    git-svn-id: http://svn.webkit.org/repository/webkit/trunk@7146 268f45cc-cd09-0410-ab3c-d52691b4dbfc

diff --git a/WebCore/ChangeLog-2005-08-23 b/WebCore/ChangeLog-2005-08-23
index bd459da..5f80576 100644
--- a/WebCore/ChangeLog-2005-08-23
+++ b/WebCore/ChangeLog-2005-08-23
@@ -1,54 +1,60 @@
-2004-07-28  Ken Kocienda  <kocienda at apple.com>
-
-        Reviewed by John
+2004-07-28  Trey Matteson  <trey at apple.com>
 
-        Export a couple more symbols for test programs.
+	Spellchecking, Part I.  Basic spellcheck is working.  Spelling panel is hooked up.
 
-        * WebCore-combined.exp:
-        * WebCore-tests.exp:
+	At this point, no special marking of misspellings, no grammar check, no context
+	menu integration, no "check continually" mode.
 
-2004-07-28  Richard Williamson   <rjw at apple.com>
+	Much of the TextIterator and CharacterIterator interface got published outside
+	of khtml_text_operations.cpp, with a little API rationalizing.
 
-        We're changing the way color is specified as a parameter in the
-        <canvas> API.  Colors are now specified using the CSS color
-        functions (or old style names or "#").  For example
-        'context.setStrokeColor ("rgba(128,128,128,0.5)")'.  Most of the
-        patch is cleanup of duplicated code in cssparser.cpp and a new
-        static function that'll crank up the CSS parser to parse the
-        color. This patch leaves the old mechanisms in place for now, so
-        we don't break any existing gadgets.
-
-        Reviewed by John.
-
-        * WebCore.pbproj/project.pbxproj:
-        * khtml/css/cssparser.cpp:
-        (CSSParser::CSSParser):
-        (CSSParser::setupParser):
-        (CSSParser::parseSheet):
-        (CSSParser::parseRule):
-        (CSSParser::parseValue):
-        (CSSParser::parseColor):
-        (CSSParser::parseDeclaration):
-        * khtml/css/cssparser.h:
-        * khtml/ecma/kjs_html.cpp:
-        (KJS::Context2DFunction::tryCall):
-
-        * kwq/KWQColor.h:
-        (QColor::alpha):
-	Added alpha() function to QColor, use instead of quirky
-	qAlpha() global function.
-
-2004-07-28  Ken Kocienda  <kocienda at apple.com>
-
-        Reviewed by Trey
+        Reviewed by Ken.
 
-        Only apply the typing style if it is non-null and has a length.
-        This fixes a problem Trey noticed with my last checkin, where
-        typed characters were placed in empty "typing style" spans.
+        * khtml/misc/khtml_text_operations.cpp:
+        (khtml::TextIterator::range):  Name changes.
+        (khtml::CharacterIterator::CharacterIterator):  Ditto.
+        (khtml::CharacterIterator::range):  Ditto.
+        (khtml::CharacterIterator::advance):  Ditto.
+        (khtml::CharacterIterator::string):  New method to consume chars into a string.
+        (khtml::WordAwareIterator::WordAwareIterator):  New class that iterates over
+	the text respecting word boundaries.
+        (khtml::WordAwareIterator::advance):
+        (khtml::WordAwareIterator::length):
+        (khtml::WordAwareIterator::characters):
+        (khtml::plainText):  Name changes.
+        (khtml::findPlainText):  Ditto.
+
+	API moved from cpp to header file.
+        * khtml/misc/khtml_text_operations.h:
+        (khtml::TextIterator::atEnd):
+        (khtml::TextIterator::length):
+        (khtml::TextIterator::characters):
+        (khtml::CharacterIterator::atBreak):
+        (khtml::CharacterIterator::atEnd):
+        (khtml::CharacterIterator::length):
+        (khtml::CharacterIterator::characters):
+        (khtml::CharacterIterator::characterOffset):
+        (khtml::WordAwareIterator::atEnd):
+        (khtml::WordAwareIterator::range):
 
-        * khtml/editing/htmlediting_impl.cpp:
-        (khtml::InputNewlineCommandImpl::doApply)
-        (khtml::InputTextCommandImpl::prepareForTextInsertion)
+        * khtml/xml/dom_position.cpp:
+        (DOM::Position::previousWordBoundary):  New name for the old routine.  This routines semantics
+	match the current behavior of this code.
+        (DOM::Position::nextWordBoundary):  Ditto.
+        (DOM::Position::previousWordPosition):  Call old code with the new name.  When we fix
+	word advancement, this routine will have its own impl.
+        (DOM::Position::nextWordPosition):  Ditto.
+        * khtml/xml/dom_position.h:
+        * khtml/xml/dom_selection.h:
+        (DOM::Selection::rangeStart):  New convenience methods
+        (DOM::Selection::rangeEnd):
+        * kwq/KWQKHTMLPart.h:
+        * kwq/KWQKHTMLPart.mm:
+        (KWQKHTMLPart::findString):  Ensure we use range-compatible positions.
+        (KWQKHTMLPart::advanceToNextMisspelling):  Brand new.
+        * kwq/WebCoreBridge.h:
+        * kwq/WebCoreBridge.mm:
+        (-[WebCoreBridge advanceToNextMisspelling]):  Typical bridge glue.
 
 2004-07-28  Ken Kocienda  <kocienda at apple.com>
 
diff --git a/WebCore/khtml/editing/SelectionController.h b/WebCore/khtml/editing/SelectionController.h
index 2b77862..ba2a8f9 100644
--- a/WebCore/khtml/editing/SelectionController.h
+++ b/WebCore/khtml/editing/SelectionController.h
@@ -81,6 +81,9 @@ public:
     Position extent() const { return m_extent; }
     Position start() const { return m_start; }
     Position end() const { return m_end; }
+    // These values are suitable for use in a DOM::Range.  The previous ones may not be.
+    Position rangeStart() const { return m_start.equivalentRangeCompliantPosition(); }
+    Position rangeEnd() const { return m_end.equivalentRangeCompliantPosition(); }
 
     QRect getRepaintRect() const;
     void setNeedsLayout(bool flag=true);
diff --git a/WebCore/khtml/editing/selection.h b/WebCore/khtml/editing/selection.h
index 2b77862..ba2a8f9 100644
--- a/WebCore/khtml/editing/selection.h
+++ b/WebCore/khtml/editing/selection.h
@@ -81,6 +81,9 @@ public:
     Position extent() const { return m_extent; }
     Position start() const { return m_start; }
     Position end() const { return m_end; }
+    // These values are suitable for use in a DOM::Range.  The previous ones may not be.
+    Position rangeStart() const { return m_start.equivalentRangeCompliantPosition(); }
+    Position rangeEnd() const { return m_end.equivalentRangeCompliantPosition(); }
 
     QRect getRepaintRect() const;
     void setNeedsLayout(bool flag=true);
diff --git a/WebCore/khtml/editing/visible_text.cpp b/WebCore/khtml/editing/visible_text.cpp
index 8f233ee..9f93654 100644
--- a/WebCore/khtml/editing/visible_text.cpp
+++ b/WebCore/khtml/editing/visible_text.cpp
@@ -28,6 +28,8 @@
 #include <misc/htmltags.h>
 #include <rendering/render_text.h>
 #include <xml/dom_nodeimpl.h>
+#include <xml/dom_position.h>
+#include <xml/dom2_rangeimpl.h>
 
 using DOM::DOMString;
 using DOM::Node;
@@ -38,88 +40,6 @@ namespace khtml {
 
 const unsigned short nonBreakingSpace = 0xA0;
 
-// Iterates through the DOM range, returning all the text, and 0-length boundaries
-// at points where replaced elements break up the text flow.
-class TextIterator
-{
-public:
-    TextIterator();
-    explicit TextIterator(const DOM::Range &);
-
-    bool atEnd() const { return !m_positionNode; }
-    void advance();
-
-    long textLength() const { return m_textLength; }
-    const QChar *textCharacters() const { return m_textCharacters; }
-
-    DOM::Range position() const;
-
-private:
-    void exitNode();
-    bool handleTextNode();
-    bool handleReplacedElement();
-    bool handleNonTextNode();
-    void handleTextBox();
-    void emitCharacter(QChar, DOM::NodeImpl *textNode, long textStartOffset, long textEndOffset);
-
-    // 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_endNode;
-    long m_endOffset;
-
-    // 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 when there is still some pending text from the current node; when these
-    // are false and 0, we go back to normal iterating.
-    bool m_needAnotherNewline;
-    InlineTextBox *m_textBox;
-
-    // Used to do the whitespace collapsing logic.
-    DOM::NodeImpl *m_lastTextNode;    
-    bool m_lastTextNodeEndedWithCollapsedSpace;
-    QChar m_lastCharacter;
-
-    // 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.
-class CharacterIterator {
-public:
-    CharacterIterator();
-    explicit CharacterIterator(const DOM::Range &r);
-
-    void advance(long numCharacters);
-
-    bool atBreak() const { return m_atBreak; }
-    bool atEnd() const { return m_textIterator.atEnd(); }
-
-    long numCharacters() const { return m_textIterator.textLength() - m_runOffset; }
-    const QChar *characters() const { return m_textIterator.textCharacters() + m_runOffset; }
-
-    long characterOffset() const { return m_offset; }
-    Range position() const;
-
-private:
-    long m_offset;
-    long m_runOffset;
-    bool m_atBreak;
-
-    TextIterator m_textIterator;
-};
-
 // Buffer that knows how to compare with a search target.
 // Keeps enough of the previous text to be able to search in the future,
 // but no more.
@@ -522,7 +442,7 @@ void TextIterator::emitCharacter(QChar c, NodeImpl *textNode, long textStartOffs
     m_lastCharacter = c;
 }
 
-Range TextIterator::position() const
+Range TextIterator::range() const
 {
     assert(m_positionNode);
     return Range(m_positionNode, m_positionStartOffset, m_positionNode, m_positionEndOffset);
@@ -536,15 +456,15 @@ CharacterIterator::CharacterIterator()
 CharacterIterator::CharacterIterator(const Range &r)
     : m_offset(0), m_runOffset(0), m_atBreak(true), m_textIterator(r)
 {
-    while (!atEnd() && m_textIterator.textLength() == 0) {
+    while (!atEnd() && m_textIterator.length() == 0) {
         m_textIterator.advance();
     }
 }
 
-Range CharacterIterator::position() const
+Range CharacterIterator::range() const
 {
-    Range r = m_textIterator.position();
-    if (m_textIterator.textLength() <= 1) {
+    Range r = m_textIterator.range();
+    if (m_textIterator.length() <= 1) {
         assert(m_runOffset == 0);
     } else {
         Node n = r.startContainer();
@@ -562,7 +482,7 @@ void CharacterIterator::advance(long count)
 
     m_atBreak = false;
 
-    long remaining = m_textIterator.textLength() - m_runOffset;
+    long remaining = m_textIterator.length() - m_runOffset;
     if (count < remaining) {
         m_runOffset += count;
         m_offset += count;
@@ -572,7 +492,7 @@ void CharacterIterator::advance(long count)
     count -= remaining;
     m_offset += remaining;
     for (m_textIterator.advance(); !atEnd(); m_textIterator.advance()) {
-        long runLength = m_textIterator.textLength();
+        long runLength = m_textIterator.length();
         if (runLength == 0) {
             m_atBreak = true;
         } else {
@@ -590,6 +510,115 @@ void CharacterIterator::advance(long count)
     m_runOffset = 0;
 }
 
+QString CharacterIterator::string(long numChars)
+{
+    QString result;
+    result.reserve(numChars);
+    while (numChars > 0 && !atEnd()) {
+        long runSize = kMin(numChars, length());
+        result.append(characters(), runSize);
+        numChars -= runSize;
+        advance(runSize);
+    }
+    return result;
+}
+
+WordAwareIterator::WordAwareIterator()
+: m_previousText(0), m_didLookAhead(false)
+{
+}
+
+WordAwareIterator::WordAwareIterator(const Range &r)
+: m_previousText(0), m_didLookAhead(false), m_textIterator(r)
+{
+    if (!m_textIterator.atEnd()) {
+        m_didLookAhead = true;  // so we consider the first chunk from the text iterator
+        advance();              // get in position over the first chunk of text
+    }
+}
+
+// We're always in one of these modes:
+// - The current chunk in the text iterator is our current chunk
+//      (typically its a piece of whitespace, or text that ended with whitespace)
+// - The previous chunk in the text iterator is our current chunk
+//      (we looked ahead to the next chunk and found a word boundary)
+// - We built up our own chunk of text from many chunks from the text iterator
+
+//FIXME: Perf could be bad for huge spans next to each other that don't fall on word boundaries
+
+void WordAwareIterator::advance()
+{
+    m_previousText = 0;
+    m_buffer = "";      // toss any old buffer we built up
+
+    // If last time we did a look-ahead, start with that looked-ahead chunk now
+    if (!m_didLookAhead) {
+        assert(!m_textIterator.atEnd());
+        m_textIterator.advance();
+    }
+    m_didLookAhead = false;
+
+    // Go to next non-empty chunk 
+    while (!m_textIterator.atEnd() && m_textIterator.length() == 0) {
+        m_textIterator.advance();
+    }
+
+    if (m_textIterator.atEnd()) {
+        return;
+    }
+    m_range = m_textIterator.range();
+    
+    while (1) {
+        // If this chunk ends in whitespace we can just use it as our chunk.
+        if (m_textIterator.characters()[m_textIterator.length()-1].isSpace()) {
+            return;
+        }
+
+        // If this is the first chunk that failed, save it in previousText before look ahead
+        if (m_buffer.isEmpty()) {
+            m_previousText = m_textIterator.characters();
+            m_previousLength = m_textIterator.length();
+        }
+
+        // Look ahead to next chunk.  If it is whitespace or a break, we can use the previous stuff
+        m_textIterator.advance();
+        if (m_textIterator.atEnd() || m_textIterator.length() == 0 || m_textIterator.characters()[0].isSpace()) {
+            m_didLookAhead = true;
+            return;
+        }
+
+        if (m_buffer.isEmpty()) {
+            // Start gobbling chunks until we get to a suitable stopping point
+            m_buffer.append(m_previousText, m_previousLength);
+            m_previousText = 0;
+        }
+        m_buffer.append(m_textIterator.characters(), m_textIterator.length());
+        m_range.setEnd(m_textIterator.range().endContainer(), m_textIterator.range().endOffset());
+    }
+}
+
+long WordAwareIterator::length() const
+{
+    if (!m_buffer.isEmpty()) {
+        return m_buffer.length();
+    } else if (m_previousText) {
+        return m_previousLength;
+    } else {
+        return m_textIterator.length();
+    }
+}
+
+const QChar *WordAwareIterator::characters() const
+{
+    if (!m_buffer.isEmpty()) {
+        return m_buffer.unicode();
+    } else if (m_previousText) {
+        return m_previousText;
+    } else {
+        return m_textIterator.characters();
+    }
+}
+
 CircularSearchBuffer::CircularSearchBuffer(const QString &s, bool isCaseSensitive)
     : m_target(s)
 {
@@ -668,12 +697,12 @@ QString plainText(const Range &r)
     // Allocate string at the right size, rather than building it up by successive append calls.
     long length = 0;
     for (TextIterator it(r); !it.atEnd(); it.advance()) {
-        length += it.textLength();
+        length += it.length();
     }
     QString result("");
     result.reserve(length);
     for (TextIterator it(r); !it.atEnd(); it.advance()) {
-        result.append(it.textCharacters(), it.textLength());
+        result.append(it.characters(), it.length());
     }
     return result;
 }
@@ -706,7 +735,7 @@ Range findPlainText(const Range &r, const QString &s, bool forward, bool caseSen
                     }
                     buffer.clear();
                 }
-                long available = it.numCharacters();
+                long available = it.length();
                 long runLength = kMin(needed, available);
                 buffer.append(runLength, it.characters());
                 it.advance(runLength);
@@ -738,9 +767,9 @@ done:
     } else {
         CharacterIterator it(r);
         it.advance(rangeEnd.characterOffset() - buffer.length());
-        result.setStart(it.position().startContainer(), it.position().startOffset());
+        result.setStart(it.range().startContainer(), it.range().startOffset());
         it.advance(buffer.length() - 1);
-        result.setEnd(it.position().endContainer(), it.position().endOffset());
+        result.setEnd(it.range().endContainer(), it.range().endOffset());
     }
     return result;
 }
diff --git a/WebCore/khtml/editing/visible_text.h b/WebCore/khtml/editing/visible_text.h
index 23065b0..d3b5b3f 100644
--- a/WebCore/khtml/editing/visible_text.h
+++ b/WebCore/khtml/editing/visible_text.h
@@ -26,7 +26,12 @@
 #ifndef __khtml_text_operations_h__
 #define __khtml_text_operations_h__
 
-#include <dom/dom2_range.h>
+#include <qstring.h>
+#include "dom/dom2_range.h"
+
+namespace DOM {
+class NodeImpl;
+}
 
 // FIXME: This class should probably use the render tree and not the DOM tree, since elements could
 // be hidden using CSS, or additional generated content could be added.  For now, we just make sure
@@ -35,9 +40,132 @@
 
 namespace khtml {
 
+class InlineTextBox;
+
+// General utillity functions
+
 QString plainText(const DOM::Range &);
 DOM::Range findPlainText(const DOM::Range &, const QString &, bool forward, bool caseSensitive);
 
+
+// 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 TextIterator
+{
+public:
+    TextIterator();
+    explicit TextIterator(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 handleTextBox();
+    void emitCharacter(QChar, DOM::NodeImpl *textNode, long textStartOffset, long textEndOffset);
+    
+    // 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_endNode;
+    long m_endOffset;
+    
+    // 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 when there is still some pending text from the current node; when these
+    // are false and 0, we go back to normal iterating.
+    bool m_needAnotherNewline;
+    InlineTextBox *m_textBox;
+    
+    // Used to do the whitespace collapsing logic.
+    DOM::NodeImpl *m_lastTextNode;    
+    bool m_lastTextNodeEndedWithCollapsedSpace;
+    QChar m_lastCharacter;
+    
+    // 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.
+class CharacterIterator {
+public:
+    CharacterIterator();
+    explicit CharacterIterator(const DOM::Range &r);
+    
+    void advance(long numCharacters);
+    
+    bool atBreak() const { return m_atBreak; }
+    bool atEnd() const { return m_textIterator.atEnd(); }
+    
+    long length() const { return m_textIterator.length() - m_runOffset; }
+    const QChar *characters() const { return m_textIterator.characters() + m_runOffset; }
+    QString string(long numChars);
+    
+    long characterOffset() const { return m_offset; }
+    DOM::Range range() const;
+        
+private:
+    long m_offset;
+    long m_runOffset;
+    bool m_atBreak;
+    
+    TextIterator m_textIterator;
+};
+    
+
+// Very similar to the TextIterator, except that the chunks of text returned are "well behaved",
+// meaning they never end split up a word.  This is useful for spellcheck or (perhaps one day) searching.
+class WordAwareIterator {
+public:
+    WordAwareIterator();
+    explicit WordAwareIterator(const DOM::Range &r);
+
+    bool atEnd() const { return !m_didLookAhead && m_textIterator.atEnd(); }
+    void advance();
+    
+    long length() const;
+    const QChar *characters() const;
+    
+    // Range of the text we're currently returning
+    DOM::Range range() const { return m_range; }
+
+private:
+    // text from the previous chunk from the textIterator
+    const QChar *m_previousText;
+    long m_previousLength;
+
+    // many chunks from textIterator concatenated
+    QString m_buffer;
+    
+    // Did we have to look ahead in the textIterator to confirm the current chunk?
+    bool m_didLookAhead;
+
+    DOM::Range m_range;
+
+    TextIterator m_textIterator;
+};
+
 }
 
 #endif
diff --git a/WebCore/khtml/misc/khtml_text_operations.cpp b/WebCore/khtml/misc/khtml_text_operations.cpp
index 8f233ee..9f93654 100644
--- a/WebCore/khtml/misc/khtml_text_operations.cpp
+++ b/WebCore/khtml/misc/khtml_text_operations.cpp
@@ -28,6 +28,8 @@
 #include <misc/htmltags.h>
 #include <rendering/render_text.h>
 #include <xml/dom_nodeimpl.h>
+#include <xml/dom_position.h>
+#include <xml/dom2_rangeimpl.h>
 
 using DOM::DOMString;
 using DOM::Node;
@@ -38,88 +40,6 @@ namespace khtml {
 
 const unsigned short nonBreakingSpace = 0xA0;
 
-// Iterates through the DOM range, returning all the text, and 0-length boundaries
-// at points where replaced elements break up the text flow.
-class TextIterator
-{
-public:
-    TextIterator();
-    explicit TextIterator(const DOM::Range &);
-
-    bool atEnd() const { return !m_positionNode; }
-    void advance();
-
-    long textLength() const { return m_textLength; }
-    const QChar *textCharacters() const { return m_textCharacters; }
-
-    DOM::Range position() const;
-
-private:
-    void exitNode();
-    bool handleTextNode();
-    bool handleReplacedElement();
-    bool handleNonTextNode();
-    void handleTextBox();
-    void emitCharacter(QChar, DOM::NodeImpl *textNode, long textStartOffset, long textEndOffset);
-
-    // 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_endNode;
-    long m_endOffset;
-
-    // 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 when there is still some pending text from the current node; when these
-    // are false and 0, we go back to normal iterating.
-    bool m_needAnotherNewline;
-    InlineTextBox *m_textBox;
-
-    // Used to do the whitespace collapsing logic.
-    DOM::NodeImpl *m_lastTextNode;    
-    bool m_lastTextNodeEndedWithCollapsedSpace;
-    QChar m_lastCharacter;
-
-    // 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.
-class CharacterIterator {
-public:
-    CharacterIterator();
-    explicit CharacterIterator(const DOM::Range &r);
-
-    void advance(long numCharacters);
-
-    bool atBreak() const { return m_atBreak; }
-    bool atEnd() const { return m_textIterator.atEnd(); }
-
-    long numCharacters() const { return m_textIterator.textLength() - m_runOffset; }
-    const QChar *characters() const { return m_textIterator.textCharacters() + m_runOffset; }
-
-    long characterOffset() const { return m_offset; }
-    Range position() const;
-
-private:
-    long m_offset;
-    long m_runOffset;
-    bool m_atBreak;
-
-    TextIterator m_textIterator;
-};
-
 // Buffer that knows how to compare with a search target.
 // Keeps enough of the previous text to be able to search in the future,
 // but no more.
@@ -522,7 +442,7 @@ void TextIterator::emitCharacter(QChar c, NodeImpl *textNode, long textStartOffs
     m_lastCharacter = c;
 }
 
-Range TextIterator::position() const
+Range TextIterator::range() const
 {
     assert(m_positionNode);
     return Range(m_positionNode, m_positionStartOffset, m_positionNode, m_positionEndOffset);
@@ -536,15 +456,15 @@ CharacterIterator::CharacterIterator()
 CharacterIterator::CharacterIterator(const Range &r)
     : m_offset(0), m_runOffset(0), m_atBreak(true), m_textIterator(r)
 {
-    while (!atEnd() && m_textIterator.textLength() == 0) {
+    while (!atEnd() && m_textIterator.length() == 0) {
         m_textIterator.advance();
     }
 }
 
-Range CharacterIterator::position() const
+Range CharacterIterator::range() const
 {
-    Range r = m_textIterator.position();
-    if (m_textIterator.textLength() <= 1) {
+    Range r = m_textIterator.range();
+    if (m_textIterator.length() <= 1) {
         assert(m_runOffset == 0);
     } else {
         Node n = r.startContainer();
@@ -562,7 +482,7 @@ void CharacterIterator::advance(long count)
 
     m_atBreak = false;
 
-    long remaining = m_textIterator.textLength() - m_runOffset;
+    long remaining = m_textIterator.length() - m_runOffset;
     if (count < remaining) {
         m_runOffset += count;
         m_offset += count;
@@ -572,7 +492,7 @@ void CharacterIterator::advance(long count)
     count -= remaining;
     m_offset += remaining;
     for (m_textIterator.advance(); !atEnd(); m_textIterator.advance()) {
-        long runLength = m_textIterator.textLength();
+        long runLength = m_textIterator.length();
         if (runLength == 0) {
             m_atBreak = true;
         } else {
@@ -590,6 +510,115 @@ void CharacterIterator::advance(long count)
     m_runOffset = 0;
 }
 
+QString CharacterIterator::string(long numChars)
+{
+    QString result;
+    result.reserve(numChars);
+    while (numChars > 0 && !atEnd()) {
+        long runSize = kMin(numChars, length());
+        result.append(characters(), runSize);
+        numChars -= runSize;
+        advance(runSize);
+    }
+    return result;
+}
+
+WordAwareIterator::WordAwareIterator()
+: m_previousText(0), m_didLookAhead(false)
+{
+}
+
+WordAwareIterator::WordAwareIterator(const Range &r)
+: m_previousText(0), m_didLookAhead(false), m_textIterator(r)
+{
+    if (!m_textIterator.atEnd()) {
+        m_didLookAhead = true;  // so we consider the first chunk from the text iterator
+        advance();              // get in position over the first chunk of text
+    }
+}
+
+// We're always in one of these modes:
+// - The current chunk in the text iterator is our current chunk
+//      (typically its a piece of whitespace, or text that ended with whitespace)
+// - The previous chunk in the text iterator is our current chunk
+//      (we looked ahead to the next chunk and found a word boundary)
+// - We built up our own chunk of text from many chunks from the text iterator
+
+//FIXME: Perf could be bad for huge spans next to each other that don't fall on word boundaries
+
+void WordAwareIterator::advance()
+{
+    m_previousText = 0;
+    m_buffer = "";      // toss any old buffer we built up
+
+    // If last time we did a look-ahead, start with that looked-ahead chunk now
+    if (!m_didLookAhead) {
+        assert(!m_textIterator.atEnd());
+        m_textIterator.advance();
+    }
+    m_didLookAhead = false;
+
+    // Go to next non-empty chunk 
+    while (!m_textIterator.atEnd() && m_textIterator.length() == 0) {
+        m_textIterator.advance();
+    }
+
+    if (m_textIterator.atEnd()) {
+        return;
+    }
+    m_range = m_textIterator.range();
+    
+    while (1) {
+        // If this chunk ends in whitespace we can just use it as our chunk.
+        if (m_textIterator.characters()[m_textIterator.length()-1].isSpace()) {
+            return;
+        }
+
+        // If this is the first chunk that failed, save it in previousText before look ahead
+        if (m_buffer.isEmpty()) {
+            m_previousText = m_textIterator.characters();
+            m_previousLength = m_textIterator.length();
+        }
+
+        // Look ahead to next chunk.  If it is whitespace or a break, we can use the previous stuff
+        m_textIterator.advance();
+        if (m_textIterator.atEnd() || m_textIterator.length() == 0 || m_textIterator.characters()[0].isSpace()) {
+            m_didLookAhead = true;
+            return;
+        }
+
+        if (m_buffer.isEmpty()) {
+            // Start gobbling chunks until we get to a suitable stopping point
+            m_buffer.append(m_previousText, m_previousLength);
+            m_previousText = 0;
+        }
+        m_buffer.append(m_textIterator.characters(), m_textIterator.length());
+        m_range.setEnd(m_textIterator.range().endContainer(), m_textIterator.range().endOffset());
+    }
+}
+
+long WordAwareIterator::length() const
+{
+    if (!m_buffer.isEmpty()) {
+        return m_buffer.length();
+    } else if (m_previousText) {
+        return m_previousLength;
+    } else {
+        return m_textIterator.length();
+    }
+}
+
+const QChar *WordAwareIterator::characters() const
+{
+    if (!m_buffer.isEmpty()) {
+        return m_buffer.unicode();
+    } else if (m_previousText) {
+        return m_previousText;
+    } else {
+        return m_textIterator.characters();
+    }
+}
+
 CircularSearchBuffer::CircularSearchBuffer(const QString &s, bool isCaseSensitive)
     : m_target(s)
 {
@@ -668,12 +697,12 @@ QString plainText(const Range &r)
     // Allocate string at the right size, rather than building it up by successive append calls.
     long length = 0;
     for (TextIterator it(r); !it.atEnd(); it.advance()) {
-        length += it.textLength();
+        length += it.length();
     }
     QString result("");
     result.reserve(length);
     for (TextIterator it(r); !it.atEnd(); it.advance()) {
-        result.append(it.textCharacters(), it.textLength());
+        result.append(it.characters(), it.length());
     }
     return result;
 }
@@ -706,7 +735,7 @@ Range findPlainText(const Range &r, const QString &s, bool forward, bool caseSen
                     }
                     buffer.clear();
                 }
-                long available = it.numCharacters();
+                long available = it.length();
                 long runLength = kMin(needed, available);
                 buffer.append(runLength, it.characters());
                 it.advance(runLength);
@@ -738,9 +767,9 @@ done:
     } else {
         CharacterIterator it(r);
         it.advance(rangeEnd.characterOffset() - buffer.length());
-        result.setStart(it.position().startContainer(), it.position().startOffset());
+        result.setStart(it.range().startContainer(), it.range().startOffset());
         it.advance(buffer.length() - 1);
-        result.setEnd(it.position().endContainer(), it.position().endOffset());
+        result.setEnd(it.range().endContainer(), it.range().endOffset());
     }
     return result;
 }
diff --git a/WebCore/khtml/misc/khtml_text_operations.h b/WebCore/khtml/misc/khtml_text_operations.h
index 23065b0..d3b5b3f 100644
--- a/WebCore/khtml/misc/khtml_text_operations.h
+++ b/WebCore/khtml/misc/khtml_text_operations.h
@@ -26,7 +26,12 @@
 #ifndef __khtml_text_operations_h__
 #define __khtml_text_operations_h__
 
-#include <dom/dom2_range.h>
+#include <qstring.h>
+#include "dom/dom2_range.h"
+
+namespace DOM {
+class NodeImpl;
+}
 
 // FIXME: This class should probably use the render tree and not the DOM tree, since elements could
 // be hidden using CSS, or additional generated content could be added.  For now, we just make sure
@@ -35,9 +40,132 @@
 
 namespace khtml {
 
+class InlineTextBox;
+
+// General utillity functions
+
 QString plainText(const DOM::Range &);
 DOM::Range findPlainText(const DOM::Range &, const QString &, bool forward, bool caseSensitive);
 
+
+// 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 TextIterator
+{
+public:
+    TextIterator();
+    explicit TextIterator(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 handleTextBox();
+    void emitCharacter(QChar, DOM::NodeImpl *textNode, long textStartOffset, long textEndOffset);
+    
+    // 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_endNode;
+    long m_endOffset;
+    
+    // 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 when there is still some pending text from the current node; when these
+    // are false and 0, we go back to normal iterating.
+    bool m_needAnotherNewline;
+    InlineTextBox *m_textBox;
+    
+    // Used to do the whitespace collapsing logic.
+    DOM::NodeImpl *m_lastTextNode;    
+    bool m_lastTextNodeEndedWithCollapsedSpace;
+    QChar m_lastCharacter;
+    
+    // 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.
+class CharacterIterator {
+public:
+    CharacterIterator();
+    explicit CharacterIterator(const DOM::Range &r);
+    
+    void advance(long numCharacters);
+    
+    bool atBreak() const { return m_atBreak; }
+    bool atEnd() const { return m_textIterator.atEnd(); }
+    
+    long length() const { return m_textIterator.length() - m_runOffset; }
+    const QChar *characters() const { return m_textIterator.characters() + m_runOffset; }
+    QString string(long numChars);
+    
+    long characterOffset() const { return m_offset; }
+    DOM::Range range() const;
+        
+private:
+    long m_offset;
+    long m_runOffset;
+    bool m_atBreak;
+    
+    TextIterator m_textIterator;
+};
+    
+
+// Very similar to the TextIterator, except that the chunks of text returned are "well behaved",
+// meaning they never end split up a word.  This is useful for spellcheck or (perhaps one day) searching.
+class WordAwareIterator {
+public:
+    WordAwareIterator();
+    explicit WordAwareIterator(const DOM::Range &r);
+
+    bool atEnd() const { return !m_didLookAhead && m_textIterator.atEnd(); }
+    void advance();
+    
+    long length() const;
+    const QChar *characters() const;
+    
+    // Range of the text we're currently returning
+    DOM::Range range() const { return m_range; }
+
+private:
+    // text from the previous chunk from the textIterator
+    const QChar *m_previousText;
+    long m_previousLength;
+
+    // many chunks from textIterator concatenated
+    QString m_buffer;
+    
+    // Did we have to look ahead in the textIterator to confirm the current chunk?
+    bool m_didLookAhead;
+
+    DOM::Range m_range;
+
+    TextIterator m_textIterator;
+};
+
 }
 
 #endif
diff --git a/WebCore/khtml/xml/dom_position.cpp b/WebCore/khtml/xml/dom_position.cpp
index 04596c0..5d9c8c3 100644
--- a/WebCore/khtml/xml/dom_position.cpp
+++ b/WebCore/khtml/xml/dom_position.cpp
@@ -284,7 +284,7 @@ Position Position::nextCharacterPosition() const
     return *this;
 }
 
-Position Position::previousWordPosition() const
+Position Position::previousWordBoundary() const
 {
     if (isEmpty())
         return Position();
@@ -310,7 +310,7 @@ Position Position::previousWordPosition() const
     return *this;
 }
 
-Position Position::nextWordPosition() const
+Position Position::nextWordBoundary() const
 {
     if (isEmpty())
         return Position();
@@ -336,6 +336,18 @@ Position Position::nextWordPosition() const
     return *this;
 }
 
+Position Position::previousWordPosition() const
+{
+    // FIXME - need an implementation that skips to starts of words, not each boundary
+    return previousWordBoundary();
+}
+
+Position Position::nextWordPosition() const
+{
+    // FIXME - need an implementation that skips to ends of words, not each boundary
+    return nextWordBoundary();
+}
+
 Position Position::previousLinePosition(int x) const
 {
     if (!node())
diff --git a/WebCore/khtml/xml/dom_position.h b/WebCore/khtml/xml/dom_position.h
index bfde9c4..3fcef3c 100644
--- a/WebCore/khtml/xml/dom_position.h
+++ b/WebCore/khtml/xml/dom_position.h
@@ -60,10 +60,17 @@ public:
     Position nextRenderedEditablePosition() const;
     Position previousCharacterPosition() const;
     Position nextCharacterPosition() const;
+    
+    // suitable for moving by word in the UI
     Position previousWordPosition() const;
     Position nextWordPosition() const;
     Position previousLinePosition(int x) const;
     Position nextLinePosition(int x) const;
+
+    // next word boundary - would be too tedious to use in UI
+    Position previousWordBoundary() const;
+    Position nextWordBoundary() const;
+
     Position equivalentUpstreamPosition() const;
     Position equivalentDownstreamPosition() const;
     Position equivalentRangeCompliantPosition() const;
diff --git a/WebCore/khtml/xml/dom_selection.h b/WebCore/khtml/xml/dom_selection.h
index 2b77862..ba2a8f9 100644
--- a/WebCore/khtml/xml/dom_selection.h
+++ b/WebCore/khtml/xml/dom_selection.h
@@ -81,6 +81,9 @@ public:
     Position extent() const { return m_extent; }
     Position start() const { return m_start; }
     Position end() const { return m_end; }
+    // These values are suitable for use in a DOM::Range.  The previous ones may not be.
+    Position rangeStart() const { return m_start.equivalentRangeCompliantPosition(); }
+    Position rangeEnd() const { return m_end.equivalentRangeCompliantPosition(); }
 
     QRect getRepaintRect() const;
     void setNeedsLayout(bool flag=true);
diff --git a/WebCore/kwq/KWQKHTMLPart.h b/WebCore/kwq/KWQKHTMLPart.h
index 5674256..73af40c 100644
--- a/WebCore/kwq/KWQKHTMLPart.h
+++ b/WebCore/kwq/KWQKHTMLPart.h
@@ -133,6 +133,7 @@ public:
     
     void scrollToAnchor(const KURL &);
     void jumpToSelection();
+    QString advanceToNextMisspelling();
     
     void setEncoding(const QString &encoding, bool userChosen);
     void addData(const char *bytes, int length);
diff --git a/WebCore/kwq/KWQKHTMLPart.mm b/WebCore/kwq/KWQKHTMLPart.mm
index ae6f0db..471e5f2 100644
--- a/WebCore/kwq/KWQKHTMLPart.mm
+++ b/WebCore/kwq/KWQKHTMLPart.mm
@@ -92,6 +92,7 @@ using DOM::Selection;
 using DOM::TextImpl;
 
 using khtml::Cache;
+using khtml::CharacterIterator;
 using khtml::ChildFrame;
 using khtml::Decoder;
 using khtml::findPlainText;
@@ -113,6 +114,7 @@ using khtml::RenderTableCell;
 using khtml::RenderText;
 using khtml::RenderWidget;
 using khtml::VISIBLE;
+using khtml::WordAwareIterator;
 
 using KIO::Job;
 
@@ -568,9 +570,13 @@ bool KWQKHTMLPart::findString(NSString *string, bool forward, bool caseFlag, boo
     searchRange.selectNodeContents(xmlDocImpl());
     if (selectionStart()) {
         if (forward) {
-            searchRange.setStart(selectionEnd(), selectionEndOffset());
+            // Must ensure the position is on a container to be usable with a DOMRange
+            Position selEnd = selection().rangeEnd();
+            searchRange.setStart(selEnd.node(), selEnd.offset());
         } else {
-            searchRange.setEnd(selectionStart(), selectionStartOffset());
+            // Must ensure the position is on a container to be usable with a DOMRange
+            Position selStart = selection().rangeStart();
+            searchRange.setEnd(selStart.node(), selStart.offset());
         }
     }
 
@@ -878,6 +884,122 @@ void KWQKHTMLPart::jumpToSelection()
     }
 }
 
+QString KWQKHTMLPart::advanceToNextMisspelling()
+{
+    // The basic approach is to search in two phases - from the selection end to the end of the doc, and
+    // the we wrap and search from the doc start to (approximately) where we started.
+    
+    // Start at the end of the selection, search to edge of document.  Starting at the selection end makes
+    // repeated "check spelling" commands work.
+    Range searchRange(xmlDocImpl());
+    searchRange.selectNodeContents(xmlDocImpl());
+    bool startedWithSelection = false;
+    if (selectionStart()) {
+        startedWithSelection = true;
+        // Must ensure the position is on a container to be usable with a DOMRange
+        Position selEnd = selection().rangeEnd();
+        searchRange.setStart(selEnd.node(), selEnd.offset());
+    }
+    
+    // If we're not in an editable node, try to find one, make that our range to work in
+    NodeImpl *editableNodeImpl = searchRange.startContainer().handle();
+    if (!editableNodeImpl->isContentEditable()) {
+        editableNodeImpl = editableNodeImpl->nextEditable();
+        if (!editableNodeImpl) {
+            return QString();
+        }
+        searchRange.setStartBefore(Node(editableNodeImpl));
+        startedWithSelection = false;   // won't need to wrap
+    }
+    
+    // topNode defines the whole range we want to operate on 
+    Node topNode(editableNodeImpl->rootEditableElement());
+    searchRange.setEndAfter(topNode);
+
+    // Make sure start of searchRange is not in the middle of a word.  Jumping back a char and then
+    // forward by a word happens to do the trick.
+    if (startedWithSelection) {
+        Position start(searchRange.startContainer().handle(), searchRange.startOffset());
+        Position newStart = start.previousCharacterPosition();
+        if (newStart != start) {
+            newStart = newStart.nextWordBoundary();
+            // Must ensure the position is on a container to be usable with a DOMRange
+            newStart = newStart.equivalentRangeCompliantPosition();
+            searchRange.setStart(Node(newStart.node()), newStart.offset());
+        } // else we were already at the start of the editable node
+    }
+    
+    if (searchRange.collapsed()) {
+        return QString();       // nothing to search in
+    }
+    
+    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
+    WordAwareIterator it(searchRange);
+    bool wrapped = false;
+    
+    Node searchEndAfterWrapNode;
+    long searchEndAfterWrapOffset;
+    if (it.atEnd()) {
+        // it.range() is not valid if we're already at the end
+        searchEndAfterWrapNode = searchRange.startContainer();
+        searchEndAfterWrapOffset = searchRange.startOffset();
+    } else {
+        // We go to the end of our first range insted of the start of it, just to be sure
+        // we don't get foiled by any word boundary problems at the start.  It means we might
+        // do a tiny bit more searching.
+        searchEndAfterWrapNode = it.range().endContainer();
+        searchEndAfterWrapOffset = it.range().endOffset();
+    }
+
+    while (1) {
+        if (!it.atEnd()) {      // we may be starting at the end of the doc, and already by atEnd
+            const QChar *chars = it.characters();
+            long len = it.length();
+            if (len > 1 || !chars[0].isSpace()) {
+                NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:(unichar *)chars length:len freeWhenDone:NO];
+                NSRange misspelling = [checker checkSpellingOfString:chunk startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:[_bridge spellCheckerDocumentTag] wordCount:NULL];
+                [chunk release];
+                if (misspelling.length > 0) {
+                    // build up result range and string, possibly reiterating over text nodes
+                    Range misspellingRange(xmlDocImpl());
+                    CharacterIterator chars(it.range());
+                    chars.advance(misspelling.location);
+                    misspellingRange.setStart(chars.range().startContainer(), chars.range().startOffset());
+                    QString result = chars.string(misspelling.length);
+                    if (chars.atEnd()) {
+                        misspellingRange.setEnd(it.range().endContainer(), it.range().endOffset());
+                    } else {
+                        misspellingRange.setEnd(chars.range().startContainer(), chars.range().startOffset());
+                    }
+                
+                    setSelection(misspellingRange);
+                    jumpToSelection();
+                    
+                    // TODO: mark misspelling in document
+
+                    return result;
+                }
+            }
+        
+            it.advance();
+        }
+        if (it.atEnd()) {
+            if (wrapped || !startedWithSelection) {
+                break;      // finished the second range, or we did the whole doc with the first range
+            } else {
+                // we've gone from the selection to the end of doc, now wrap around
+                wrapped = YES;
+                searchRange.setStartBefore(topNode);
+                // going until the end of the very first chunk we tested is far enough
+                searchRange.setEnd(searchEndAfterWrapNode, searchEndAfterWrapOffset);
+                it = WordAwareIterator(searchRange);
+            }
+        }   
+    }
+    
+    return QString();
+}
+
 void KWQKHTMLPart::redirectionTimerStartedOrStopped()
 {
     // Don't report history navigations, just actual redirection.
diff --git a/WebCore/kwq/WebCoreBridge.h b/WebCore/kwq/WebCoreBridge.h
index 15bf0bd..1de686b 100644
--- a/WebCore/kwq/WebCoreBridge.h
+++ b/WebCore/kwq/WebCoreBridge.h
@@ -219,6 +219,7 @@ typedef enum {
 
 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag;
 - (void)jumpToSelection;
+- (NSString *)advanceToNextMisspelling;
 
 - (void)setTextSizeMultiplier:(float)multiplier;
 
@@ -486,6 +487,8 @@ typedef enum {
 
 - (void)windowObjectCleared;
 
+- (int)spellCheckerDocumentTag;
+
 @end
 
 // This interface definition allows those who hold a WebCoreBridge * to call all the methods
diff --git a/WebCore/kwq/WebCoreBridge.mm b/WebCore/kwq/WebCoreBridge.mm
index 2ec7a76..e074be6 100644
--- a/WebCore/kwq/WebCoreBridge.mm
+++ b/WebCore/kwq/WebCoreBridge.mm
@@ -1049,6 +1049,11 @@ static HTMLFormElementImpl *formElementFromDOMElement(DOMElement *element)
     _part->jumpToSelection();
 }
 
+- (NSString *)advanceToNextMisspelling
+{
+    return _part->advanceToNextMisspelling().getNSString();
+}
+
 - (void)setTextSizeMultiplier:(float)multiplier
 {
     int newZoomFactor = (int)rint(multiplier * 100);
diff --git a/WebKit/ChangeLog b/WebKit/ChangeLog
index e1e8023..34cfb40 100644
--- a/WebKit/ChangeLog
+++ b/WebKit/ChangeLog
@@ -1,3 +1,24 @@
+2004-07-28  Trey Matteson  <trey at apple.com>
+
+	Spellchecking, Part I.  Basic spellcheck is working.  Spelling panel is hooked up.
+
+	At this point, no special marking of misspellings, no grammar check, no context
+	menu integration, no "check continually" mode.
+
+        Reviewed by Ken.
+
+        * WebCoreSupport.subproj/WebBridge.m:
+        (-[WebBridge spellCheckerDocumentTag]):  Typical bridge glue.
+        * WebView.subproj/WebHTMLView.m:
+        (-[WebHTMLView validateUserInterfaceItem:]):  Validate various spelling actions.
+        (-[WebHTMLView checkSpelling:]):  Call WC for real work, update panel.
+        (-[WebHTMLView showGuessPanel:]):  Show panel, call WC for real work.
+        (-[WebHTMLView _changeSpellingToWord:]):  Apply correction to our doc.
+        (-[WebHTMLView changeSpelling:]):  Simple pass through to above method.
+        (-[WebHTMLView ignoreSpelling:]):  Tell checker to ignore the word.
+        * WebView.subproj/WebView.m:
+        (-[WebView _close]):  Call AK's closeSpellDocumentWithTag: for proper cleanup.
+
 2004-07-27  John Sullivan  <sullivan at apple.com>
 
         Reviewed by Trey.
diff --git a/WebKit/WebCoreSupport.subproj/WebBridge.m b/WebKit/WebCoreSupport.subproj/WebBridge.m
index 124bd0c..ad8d0f0 100644
--- a/WebKit/WebCoreSupport.subproj/WebBridge.m
+++ b/WebKit/WebCoreSupport.subproj/WebBridge.m
@@ -1337,4 +1337,9 @@ static id <WebFormDelegate> formDelegate(WebBridge *self)
     [[wv _frameLoadDelegateForwarder] webView:wv windowScriptObjectAvailable:[self windowScriptObject]];
 }
 
+- (int)spellCheckerDocumentTag
+{
+    return [[_frame webView] spellCheckerDocumentTag];
+}
+
 @end
diff --git a/WebKit/WebView.subproj/WebHTMLView.m b/WebKit/WebView.subproj/WebHTMLView.m
index c17f434..d93ff7a 100644
--- a/WebKit/WebView.subproj/WebHTMLView.m
+++ b/WebKit/WebView.subproj/WebHTMLView.m
@@ -1225,8 +1225,13 @@ static WebHTMLView *lastHitView = nil;
         return [self _haveSelection];
     } else if (action == @selector(jumpToSelection:)) {
         return [self _haveSelection];
+    } else if (action == @selector(checkSpelling:)
+               || action == @selector(showGuessPanel:)
+               || action == @selector(changeSpelling:)
+               || action == @selector(ignoreSpelling:)) {
+        return [[self _bridge] isSelectionEditable];
     }
-    
+
     return YES;
 }
 
@@ -2871,7 +2876,17 @@ static WebHTMLView *lastHitView = nil;
 
 - (void)checkSpelling:(id)sender
 {
-#if 0
+    // WebCore does everything but update the spelling panel
+    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
+    if (!checker) {
+        return;
+    }
+    NSString *badWord = [[self _bridge] advanceToNextMisspelling];
+    if (badWord) {
+        [checker updateSpellingPanelWithMisspelledWord:badWord];
+    }
+}
+#if APPKIT_CODE_FOR_REFERENCE
     NSTextStorage *text = _getTextStorage(self);
     NSTextViewSharedData *sharedData = _getSharedData(self);
     if (text && ([text length] > 0) && [self isSelectable]) {
@@ -2915,11 +2930,23 @@ static WebHTMLView *lastHitView = nil;
         }
     }
 #endif
-}
+
 
 - (void)showGuessPanel:(id)sender
 {
-#if 0
+    // WebCore does everything but update the spelling panel
+    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
+    if (!checker) {
+        return;
+    }
+
+    NSString *badWord = [[self _bridge] advanceToNextMisspelling];
+    if (badWord) {
+        [checker updateSpellingPanelWithMisspelledWord:badWord];
+    }
+    [[checker spellingPanel] orderFront:sender];
+}
+#if APPKIT_CODE_FOR_REFERENCE
     NSTextStorage *text = _getTextStorage(self);
     NSTextViewSharedData *sharedData = _getSharedData(self);
     NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
@@ -2944,19 +2971,33 @@ static WebHTMLView *lastHitView = nil;
         [[checker spellingPanel] orderFront:sender];
     }
 #endif
-}
 
-#if 0
+- (void)_changeSpellingToWord:(NSString *)newWord
+{
+    WebBridge *bridge = [self _bridge];
+    if (![bridge isSelectionEditable]) {
+        return;
+    }
+    
+    // Don't correct to empty string.  (AppKit checked this, we might as well too.)
+    if (![NSSpellChecker sharedSpellChecker] || [newWord isEqualToString:@""]) {
+        return;
+    }
 
-- (void)_changeSpellingToWord:(NSString *)newWord {
+    WebView *webView = [self _webView];
+    if ([[webView _editingDelegateForwarder] webView:webView shouldInsertText:newWord replacingDOMRange:[bridge selectedDOMRange] givenAction:WebViewInsertActionPasted]) {
+        [bridge replaceSelectionWithText:newWord selectReplacement:YES];
+    }
+}
+#if APPKIT_CODE_FOR_REFERENCE
     NSRange charRange = [self rangeForUserTextChange];
     if ([self isEditable] && charRange.location != NSNotFound) {
         NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
         if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
-
+        
         // Don't correct to empty string.
         if ([newWord isEqualToString:@""]) return;
-
+        
         if ([self shouldChangeTextInRange:charRange replacementString:newWord]) {
             [self replaceCharactersInRange:charRange withString:newWord];
             charRange.length = [newWord length];
@@ -2964,25 +3005,39 @@ static WebHTMLView *lastHitView = nil;
             [self didChangeText];
         }
     }
-}
-
-- (void)_changeSpellingFromMenu:(id)sender {
-    [self _changeSpellingToWord:[sender title]];
-}
+#endif
 
 - (void)changeSpelling:(id)sender {
     [self _changeSpellingToWord:[[sender selectedCell] stringValue]];
 }
 
 - (void)ignoreSpelling:(id)sender {
+    WebBridge *bridge = [self _bridge];
+    if (![bridge isSelectionEditable]) {
+        return;
+    }
+    
+    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
+    if (!checker) {
+        return;
+    }
+    
+    NSString *stringToIgnore = [sender stringValue];
+    unsigned int length = [stringToIgnore length];
+    if (stringToIgnore && length > 0) {
+        [checker ignoreWord:stringToIgnore inSpellDocumentWithTag:[[self _webView] spellCheckerDocumentTag]];
+        //??? need to clear any special markup for misspelled words
+    }
+}
+#if APPKIT_CODE_FOR_REFERENCE
     NSRange charRange = [self rangeForUserTextChange];
     if ([self isEditable]) {
         NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
         NSString *stringToIgnore;
         unsigned int length;
-
+        
         if (!checker) return;
-
+        
         stringToIgnore = [sender stringValue];
         length = [stringToIgnore length];
         if (stringToIgnore && length > 0) {
@@ -2992,6 +3047,13 @@ static WebHTMLView *lastHitView = nil;
             }
         }
     }
+#endif
+
+
+#if APPKIT_CODE_FOR_REFERENCE
+
+- (void)_changeSpellingFromMenu:(id)sender {
+    [self _changeSpellingToWord:[sender title]];
 }
 
 - (void)_ignoreSpellingFromMenu:(id)sender {
diff --git a/WebKit/WebView.subproj/WebView.m b/WebKit/WebView.subproj/WebView.m
index ac03871..3d59359 100644
--- a/WebKit/WebView.subproj/WebView.m
+++ b/WebKit/WebView.subproj/WebView.m
@@ -280,6 +280,11 @@ NSString *_WebMainFrameURLKey =         @"mainFrameURL";
     // Clear the page cache so we call destroy on all the plug-ins in the page cache to break any retain cycles.
     // See comment in [WebHistoryItem _releaseAllPendingPageCaches] for more information.
     [_private->backForwardList _clearPageCache];
+    
+    if (_private->hasSpellCheckerDocumentTag) {
+        [[NSSpellChecker sharedSpellChecker] closeSpellDocumentWithTag:_private->spellCheckerDocumentTag];
+        _private->hasSpellCheckerDocumentTag = NO;
+    }
 }
 
 - (WebFrame *)_createFrameNamed:(NSString *)fname inParent:(WebFrame *)parent allowsScrolling:(BOOL)allowsScrolling

-- 
WebKit Debian packaging



More information about the Pkg-webkit-commits mailing list