WebCore:
authorkocienda <kocienda@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 7 Aug 2004 00:25:28 +0000 (00:25 +0000)
committerkocienda <kocienda@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 7 Aug 2004 00:25:28 +0000 (00:25 +0000)
        Reviewed by Maciej

        Finish off spellchecking support to HTML editing. Includes work to
        enable continuous spellchecking.

        * khtml/editing/htmlediting_impl.cpp:
        (khtml::EditCommandImpl::markMisspellingsInSelection): Basically, a one-liner convenience to
        make the call over to the KWQKHTMLPart.
        (khtml::ReplaceSelectionCommandImpl::doApply): Did some rearranging of code so that the
        inserted content can be spell-checked. The function is basically the same, except for
        the addition of calls to markMisspellingsInSelection.
        (khtml::TypingCommandImpl::markMisspellingsAfterTyping): New function. Takes a look at the
        selection that results after typing and determines whether it needs to spellcheck.
        Since the word containing the current selection is never marked, this does a check to
        see if typing made a new word that is not in the current selection. Basically, you
        get this by being at the end of a word and typing a space.
        (khtml::TypingCommandImpl::typingAddedToOpenCommand): Call markMisspellingsAfterTyping.
        * khtml/editing/htmlediting_impl.h: Add new function declarations.
         * khtml/khtml_part.cpp:
        (KHTMLPart::setSelection): Since spell checks are updated when the selection changes,
        and every selection change passes through here, this is a good place to put the call
        to the spellchecker.
        * khtml/rendering/render_text.cpp:
        (InlineTextBox::paintMarker): Remove temporary misspelling line drawing code. Replace with
        call that does AppKit-style drawing. Fix up some comments.
        * khtml/xml/dom_docimpl.cpp:
        (DocumentImpl::addMarker): Repaint the node that had the marker added. This makes it show
        up on setting it.
        (DocumentImpl::removeMarker): Ditto.
        (DocumentImpl::removeAllMarkers): New function. Convenience for clearing all markers.
        Used when not in continuous spellchecking mode.
        (DocumentImpl::shiftMarkers): Moves markers in response to changes in a node's contents.
        This shifts the marker offsets by a given amount. This keeps the markers in the right
        place when a user types in a node with markers already set on it.
        * khtml/xml/dom_docimpl.h: Added new functions. Removed unnecessary enum qualifier from some
        declarations.
        * khtml/xml/dom_position.cpp:
        (DOM::Position::previousWordBoundary): This function was susceptible to endless loops...and
        needlessly so. Basically, if the current position is at a word boundary, run the code again
        to find the previous word boundary.
        (DOM::Position::nextWordBoundary): Same as above, but for next word boundary.
        * khtml/xml/dom_textimpl.cpp:
        (CharacterDataImpl::setData): Call shiftMarkers to update markers when this node changes.
        (CharacterDataImpl::insertData): Ditto.
        (CharacterDataImpl::deleteData): Ditto.
        (CharacterDataImpl::replaceData): Ditto.
        * kwq/KWQKHTMLPart.h:
        * kwq/KWQKHTMLPart.mm:
        (KWQKHTMLPart::advanceToNextMisspelling):
        (KWQKHTMLPart::markMisspellingsInSelection):
        (KWQKHTMLPart::updateSpellChecking):
        (KWQKHTMLPart::respondToChangedSelection):
        * kwq/KWQPainter.h:
        * kwq/KWQPainter.mm:
        (QPainter::drawLineForMisspelling): New function. Call over to WebKit to do the drawing.
        * kwq/WebCoreBridge.h:
        * kwq/WebCoreBridge.mm:
        (-[WebCoreBridge alterCurrentSelection:direction:granularity:]): Pass markMisspellings flag to
        setSelection call.
        * kwq/WebCoreTextRenderer.h:

WebKit:

        Reviewed by Maciej

        Finish off spellchecking support to HTML editing. Includes work to
        enable continuous spellchecking.

        * WebCoreSupport.subproj/WebBridge.m:
        (-[WebBridge isContinuousSpellCheckingEnabled]): Simple bridge method.
        * WebCoreSupport.subproj/WebTextRenderer.m:
        (-[WebTextRenderer drawLineForMisspelling:withWidth:]): New method to add
        AppKit-style misspelling underline.

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

19 files changed:
WebCore/ChangeLog-2005-08-23
WebCore/khtml/editing/htmlediting_impl.cpp
WebCore/khtml/editing/htmlediting_impl.h
WebCore/khtml/khtml_part.cpp
WebCore/khtml/rendering/render_text.cpp
WebCore/khtml/xml/dom_docimpl.cpp
WebCore/khtml/xml/dom_docimpl.h
WebCore/khtml/xml/dom_position.cpp
WebCore/khtml/xml/dom_textimpl.cpp
WebCore/kwq/KWQKHTMLPart.h
WebCore/kwq/KWQKHTMLPart.mm
WebCore/kwq/KWQPainter.h
WebCore/kwq/KWQPainter.mm
WebCore/kwq/WebCoreBridge.h
WebCore/kwq/WebCoreBridge.mm
WebCore/kwq/WebCoreTextRenderer.h
WebKit/ChangeLog
WebKit/WebCoreSupport.subproj/WebBridge.m
WebKit/WebCoreSupport.subproj/WebTextRenderer.m

index d9fc1667b1d6e00b70f119d4f2d00e66871e71e0..82b50e163dc213185900c0faa66cc08d9a69044e 100644 (file)
@@ -1,3 +1,66 @@
+2004-08-06  Ken Kocienda  <kocienda@apple.com>
+
+        Reviewed by Maciej
+
+        Finish off spellchecking support to HTML editing. Includes work to
+        enable continuous spellchecking.
+
+        * khtml/editing/htmlediting_impl.cpp:
+        (khtml::EditCommandImpl::markMisspellingsInSelection): Basically, a one-liner convenience to
+        make the call over to the KWQKHTMLPart.
+        (khtml::ReplaceSelectionCommandImpl::doApply): Did some rearranging of code so that the
+        inserted content can be spell-checked. The function is basically the same, except for
+        the addition of calls to markMisspellingsInSelection.
+        (khtml::TypingCommandImpl::markMisspellingsAfterTyping): New function. Takes a look at the
+        selection that results after typing and determines whether it needs to spellcheck. 
+        Since the word containing the current selection is never marked, this does a check to
+        see if typing made a new word that is not in the current selection. Basically, you
+        get this by being at the end of a word and typing a space.
+        (khtml::TypingCommandImpl::typingAddedToOpenCommand): Call markMisspellingsAfterTyping.
+        * khtml/editing/htmlediting_impl.h: Add new function declarations.
+         * khtml/khtml_part.cpp:
+        (KHTMLPart::setSelection): Since spell checks are updated when the selection changes, 
+        and every selection change passes through here, this is a good place to put the call 
+        to the spellchecker.
+        * khtml/rendering/render_text.cpp:
+        (InlineTextBox::paintMarker): Remove temporary misspelling line drawing code. Replace with
+        call that does AppKit-style drawing. Fix up some comments.
+        * khtml/xml/dom_docimpl.cpp:
+        (DocumentImpl::addMarker): Repaint the node that had the marker added. This makes it show 
+        up on setting it.
+        (DocumentImpl::removeMarker): Ditto.
+        (DocumentImpl::removeAllMarkers): New function. Convenience for clearing all markers.
+        Used when not in continuous spellchecking mode.
+        (DocumentImpl::shiftMarkers): Moves markers in response to changes in a node's contents.
+        This shifts the marker offsets by a given amount. This keeps the markers in the right
+        place when a user types in a node with markers already set on it.
+        * khtml/xml/dom_docimpl.h: Added new functions. Removed unnecessary enum qualifier from some
+        declarations.
+        * khtml/xml/dom_position.cpp:
+        (DOM::Position::previousWordBoundary): This function was susceptible to endless loops...and
+        needlessly so. Basically, if the current position is at a word boundary, run the code again
+        to find the previous word boundary.
+        (DOM::Position::nextWordBoundary): Same as above, but for next word boundary.
+        * khtml/xml/dom_textimpl.cpp:
+        (CharacterDataImpl::setData): Call shiftMarkers to update markers when this node changes.
+        (CharacterDataImpl::insertData): Ditto.
+        (CharacterDataImpl::deleteData): Ditto.
+        (CharacterDataImpl::replaceData): Ditto.
+        * kwq/KWQKHTMLPart.h:
+        * kwq/KWQKHTMLPart.mm:
+        (KWQKHTMLPart::advanceToNextMisspelling):
+        (KWQKHTMLPart::markMisspellingsInSelection):
+        (KWQKHTMLPart::updateSpellChecking):
+        (KWQKHTMLPart::respondToChangedSelection):
+        * kwq/KWQPainter.h:
+        * kwq/KWQPainter.mm:
+        (QPainter::drawLineForMisspelling): New function. Call over to WebKit to do the drawing.
+        * kwq/WebCoreBridge.h:
+        * kwq/WebCoreBridge.mm:
+        (-[WebCoreBridge alterCurrentSelection:direction:granularity:]): Pass markMisspellings flag to
+        setSelection call. 
+        * kwq/WebCoreTextRenderer.h:
+
 === Safari-155 ===
 
 2004-08-05  David Hyatt  <hyatt@apple.com>
index a593ce2a462bfa1bf5caec1922e1b293e5ecf7de..88d89a7f2dc3c9716155f3f4c237b93efb1baa0e 100644 (file)
@@ -52,6 +52,7 @@
 #if APPLE_CHANGES
 #include "KWQAssertions.h"
 #include "KWQLogging.h"
+#include "KWQKHTMLPart.h"
 #endif
 
 using DOM::AttrImpl;
@@ -322,6 +323,11 @@ void EditCommandImpl::setEndingSelection(const Selection &s)
     }
 }
 
+void EditCommandImpl::markMisspellingsInSelection(const Selection &s)
+{
+    KWQ(document()->part())->markMisspellingsInSelection(s);
+}
+
 EditCommand EditCommandImpl::parent() const
 {
     return m_parent;
@@ -2005,10 +2011,15 @@ void ReplaceSelectionCommandImpl::doApply()
         ASSERT(!lastChild);
     } else if (firstChild == lastChild && firstChild->isTextNode()) {
         // Simple text paste. Treat as if the text were typed.
-        Position base = selection.base();
+        Position upstreamStart(selection.start().equivalentUpstreamPosition());
         inputText(static_cast<TextImpl *>(firstChild)->data());
         if (m_selectReplacement) {
-            setEndingSelection(Selection(base, endingSelection().extent()));
+            // Select what was inserted.
+            setEndingSelection(Selection(selection.base(), endingSelection().extent()));
+        }
+        else {
+            // Mark misspellings in the inserted content.
+            markMisspellingsInSelection(Selection(upstreamStart, endingSelection().extent()));
         }
     } 
     else {
@@ -2035,21 +2046,26 @@ void ReplaceSelectionCommandImpl::doApply()
                 break;
             lastLeaf = nextChild;
         }
+
+        // Find the first leaf.
+        NodeImpl *firstLeaf = firstChild;
+        while (1) {
+            NodeImpl *nextChild = firstLeaf->firstChild();
+            if (!nextChild)
+                break;
+            firstLeaf = nextChild;
+        }
         
-       if (m_selectReplacement) {            
-            // Find the first leaf.
-            NodeImpl *firstLeaf = firstChild;
-            while (1) {
-                NodeImpl *nextChild = firstLeaf->firstChild();
-                if (!nextChild)
-                    break;
-                firstLeaf = nextChild;
-            }
+        Selection replacementSelection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset()));
+       if (m_selectReplacement) {
             // Select what was inserted.
-            setEndingSelection(Selection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset())));
-        } else {
-            // Place the cursor after what was inserted.
-            setEndingSelection(Position(lastLeaf, lastLeaf->caretMaxOffset()));
+            setEndingSelection(replacementSelection);
+        } 
+        else {
+            // Place the cursor after what was inserted, and mark misspellings in the inserted content.
+            selection = Selection(Position(lastLeaf, lastLeaf->caretMaxOffset()));
+            setEndingSelection(selection);
+            markMisspellingsInSelection(replacementSelection);
         }
     }
 }
@@ -2461,10 +2477,24 @@ void TypingCommandImpl::doApply()
     }
 }
 
+void TypingCommandImpl::markMisspellingsAfterTyping()
+{
+    // Take a look at the selection that results after typing and determine whether we need to spellcheck. 
+    // Since the word containing the current selection is never marked, this does a check to
+    // see if typing made a new word that is not in the current selection. Basically, you
+    // get this by being at the end of a word and typing a space.    
+    Position start(endingSelection().start());
+    Position p1 = start.previousCharacterPosition().previousWordBoundary();
+    Position p2 = start.previousWordBoundary();
+    if (p1 != p2)
+        markMisspellingsInSelection(Selection(start.previousCharacterPosition()));
+}
+
 void TypingCommandImpl::typingAddedToOpenCommand()
 {
     ASSERT(document());
     ASSERT(document()->part());
+    markMisspellingsAfterTyping();
     EditCommand cmd(this);
     document()->part()->appliedEditing(cmd);
 }
index 94e4632637c744a3144c6d9236ba9366d165a493..a339bbbc1f0a78a5be0e5236a793b91bcc41417b 100644 (file)
@@ -88,6 +88,8 @@ public:
 
     void setStartingSelection(const DOM::Selection &s);
     void setEndingSelection(const DOM::Selection &s);
+    
+    void markMisspellingsInSelection(const DOM::Selection &s);
 
 private:
     DOM::DocumentImpl *m_document;
@@ -612,6 +614,7 @@ public:
 private:
     void issueCommandForDeleteKey();
     void removeCommand(const EditCommand &);
+    void markMisspellingsAfterTyping();
     void typingAddedToOpenCommand();
     
     TypingCommand::ETypingCommand m_commandType;
index 002e9aa3c147fbbf41832ba5d6cdc981068f3743..ea2d5fcf809c68d3b615ce4162ba464b4b54687e 100644 (file)
@@ -2286,6 +2286,10 @@ void KHTMLPart::setSelection(const Selection &s, bool closeTyping)
     if (d->m_selection != s) {
         clearCaretRectIfNeeded(); 
         setFocusNodeIfNeeded(s);
+#if APPLE_CHANGES
+        // Mark mispellings in the soon-to-be previous selection.
+        KWQ(this)->markMisspellingsInSelection(d->m_selection);
+#endif
         d->m_selection = s;
         notifySelectionChanged(closeTyping);
     }
index 63faa890c16e775268fc4f54620971cac39c99b3..112151399c04c89518e53ee5acc5e5be480f199a 100644 (file)
@@ -288,13 +288,13 @@ void InlineTextBox::paintMarker(QPainter *pt, int _tx, int _ty, DocumentMarker m
     int width = m_width;            // how much line to draw
     bool useWholeWidth = true;
     ulong paintStart = m_start;
-    ulong paintEnd = end()+1;      // end doesn't points at the last char, not past it
+    ulong paintEnd = end()+1;      // end points at the last char, not past it
     if (paintStart != marker.startOffset) {
         paintStart = marker.startOffset;
         useWholeWidth = false;
         start = static_cast<RenderText*>(m_object)->width(m_start, paintStart - m_start, m_firstLine);
     }
-    if (paintEnd != marker.endOffset) {      // end doesn't points at the last char, not past it
+    if (paintEnd != marker.endOffset) {      // end points at the last char, not past it
         paintEnd = kMin(paintEnd, marker.endOffset);
         useWholeWidth = false;
     }
@@ -306,12 +306,9 @@ void InlineTextBox::paintMarker(QPainter *pt, int _tx, int _ty, DocumentMarker m
         width = static_cast<RenderText*>(m_object)->width(paintStart, paintEnd - paintStart, m_firstLine);
     }
 
-    int underlineOffset = ( pt->fontMetrics().height() + m_baseline ) / 2;
-    if(underlineOffset <= m_baseline) {
-        underlineOffset = m_baseline+1;
-    }
-    pt->setPen(QColor(0, 255, 0));  // FIXME - use AppKit pattern-based code to draw
-    pt->drawLine(_tx + start, _ty + underlineOffset, _tx + start + width, _ty + underlineOffset );
+    // AppKit uses a two-pixel offset from the baseline for the misspelling underline.
+    int underlineOffset = m_baseline + 2;
+    pt->drawLineForMisspelling(_tx + start, _ty + underlineOffset, width);
 }
 
 long InlineTextBox::caretMinOffset() const
index 5566d1c0377ab4f67944ded1d73b2c792c67d78e..58aee9d7feec4ed4f8753fb47c7c5ed4d09729c6 100644 (file)
@@ -2928,6 +2928,10 @@ void DocumentImpl::addMarker(NodeImpl *node, DocumentMarker newMarker)
         // at this point it points to the node before which we want to insert
         markers->insert(it, newMarker);
     }
+    
+    // repaint the affected node
+    if (node->renderer())
+        node->renderer()->repaint();
 }
 
 void DocumentImpl::removeMarker(NodeImpl *node, DocumentMarker target)
@@ -2972,6 +2976,51 @@ void DocumentImpl::removeMarker(NodeImpl *node, DocumentMarker target)
             }
         }
     }
+
+    // repaint the affected node
+    if (node->renderer())
+        node->renderer()->repaint();
+}
+
+void DocumentImpl::removeAllMarkers(NodeImpl *node)
+{
+    QValueList <DocumentMarker> *markers = m_markers.find(node);
+    if (!markers)
+        return;
+
+    QValueListIterator<DocumentMarker> it;
+    for (it = markers->begin(); it != markers->end(); ++it) {
+        markers->remove(it);
+    }
+}
+
+void DocumentImpl::removeAllMarkers()
+{
+    m_markers.clear();
+}
+
+void DocumentImpl::shiftMarkers(NodeImpl *node, ulong startOffset, long delta)
+{
+    if (m_markers.isEmpty())
+        return;
+
+    QValueList <DocumentMarker> *markers = m_markers.find(node);
+    if (!markers)
+        return;
+
+    QValueListIterator<DocumentMarker> it;
+    for (it = markers->begin(); it != markers->end(); ++it) {
+        DocumentMarker &marker = *it;
+        if (marker.startOffset >= startOffset) {
+            ASSERT(marker.startOffset + delta > 0);
+            marker.startOffset += delta;
+            marker.endOffset += delta;
+        }
+    }
+    
+    // repaint the affected node
+    if (node->renderer())
+        node->renderer()->repaint();
 }
 
 QValueList<DocumentMarker> DocumentImpl::markersForNode(NodeImpl *node)
index a6a04201df12afb8f2e06f0cf9118e1c26fa7e93..c883a44e598c96589d238c6dbf82ec14755b9cfe 100644 (file)
@@ -544,10 +544,13 @@ public:
     bool queryCommandSupported(const DOMString &command);
     DOMString queryCommandValue(const DOMString &command);
     
-    void addMarker(Range range, enum DocumentMarker::MarkerType type);
-    void removeMarker(Range range, enum DocumentMarker::MarkerType type);
+    void addMarker(Range range, DocumentMarker::MarkerType type);
+    void removeMarker(Range range, DocumentMarker::MarkerType type);
     void addMarker(NodeImpl *node, DocumentMarker marker);
     void removeMarker(NodeImpl *node, DocumentMarker marker);
+    void removeAllMarkers(NodeImpl *node);
+    void removeAllMarkers();
+    void shiftMarkers(NodeImpl *node, ulong startOffset, long delta);
     QValueList<DocumentMarker> markersForNode(NodeImpl *node);
 
 #ifndef KHTML_NO_XBL
index 46fd376546990f68e593f4bc688c66ce08befb47..432f086b07c299a0019bb759b23f556abbb74412 100644 (file)
@@ -290,21 +290,22 @@ Position Position::previousWordBoundary() const
         return Position();
 
     Position pos = *this;
-    for (PositionIterator it(*this); !it.atStart(); it.previous()) {
-        if (it.current().node()->nodeType() == Node::TEXT_NODE || it.current().node()->nodeType() == Node::CDATA_SECTION_NODE) {
-            DOMString t = it.current().node()->nodeValue();
+    int tries = 0;
+    while (tries < 2) {
+        if (pos.node()->nodeType() == Node::TEXT_NODE || pos.node()->nodeType() == Node::CDATA_SECTION_NODE) {
+            DOMString t = pos.node()->nodeValue();
             QChar *chars = t.unicode();
             uint len = t.length();
             int start, end;
-            khtml::findWordBoundary(chars, len, it.current().offset(), &start, &end);
-            pos = Position(it.current().node(), start);
+            khtml::findWordBoundary(chars, len, pos.offset(), &start, &end);
+            pos = Position(pos.node(), start);
         }
         else {
-            pos = Position(it.current().node(), it.current().node()->caretMinOffset());
+            pos = Position(pos.node(), pos.node()->caretMinOffset());
         }
         if (pos != *this)
             return pos;
-        it.setPosition(pos);
+        tries++;
     }
     
     return *this;
@@ -316,21 +317,22 @@ Position Position::nextWordBoundary() const
         return Position();
 
     Position pos = *this;
-    for (PositionIterator it(*this); !it.atEnd(); it.next()) {
-        if (it.current().node()->nodeType() == Node::TEXT_NODE || it.current().node()->nodeType() == Node::CDATA_SECTION_NODE) {
-            DOMString t = it.current().node()->nodeValue();
+    int tries = 0;
+    while (tries < 2) {
+        if (pos.node()->nodeType() == Node::TEXT_NODE || pos.node()->nodeType() == Node::CDATA_SECTION_NODE) {
+            DOMString t = pos.node()->nodeValue();
             QChar *chars = t.unicode();
             uint len = t.length();
             int start, end;
-            khtml::findWordBoundary(chars, len, it.current().offset(), &start, &end);
-            pos = Position(it.current().node(), end);
+            khtml::findWordBoundary(chars, len, pos.offset(), &start, &end);
+            pos = Position(pos.node(), end);
         }
         else {
-            pos = Position(it.current().node(), it.current().node()->caretMaxOffset());
+            pos = Position(pos.node(), pos.node()->caretMaxOffset());
         }
         if (pos != *this)
             return pos;
-        it.setPosition(pos);
+        tries++;
     }
     
     return *this;
index 02b91f5b8750c38d1faeb35fa0603167dbe6f025..84b7ca6da22c281d04d5545bdabe33086d4d1e99 100644 (file)
@@ -76,6 +76,8 @@ void CharacterDataImpl::setData( const DOMString &_data, int &exceptioncode )
     
     dispatchModifiedEvent(oldStr);
     if(oldStr) oldStr->deref();
+    
+    getDocument()->removeAllMarkers(this);
 }
 
 unsigned long CharacterDataImpl::length() const
@@ -130,6 +132,10 @@ void CharacterDataImpl::insertData( const unsigned long offset, const DOMString
     
     dispatchModifiedEvent(oldStr);
     oldStr->deref();
+    
+    // update the markers for spell checking and grammar checking
+    uint length = arg.length();
+    getDocument()->shiftMarkers(this, offset, length);
 }
 
 void CharacterDataImpl::deleteData( const unsigned long offset, const unsigned long count, int &exceptioncode )
@@ -148,6 +154,9 @@ void CharacterDataImpl::deleteData( const unsigned long offset, const unsigned l
     
     dispatchModifiedEvent(oldStr);
     oldStr->deref();
+
+    // update the markers for spell checking and grammar checking
+    getDocument()->shiftMarkers(this, offset + count, -count);
 }
 
 void CharacterDataImpl::replaceData( const unsigned long offset, const unsigned long count, const DOMString &arg, int &exceptioncode )
@@ -173,6 +182,10 @@ void CharacterDataImpl::replaceData( const unsigned long offset, const unsigned
     
     dispatchModifiedEvent(oldStr);
     oldStr->deref();
+    
+    // update the markers for spell checking and grammar checking
+    int diff = arg.length() - count;
+    getDocument()->shiftMarkers(this, offset + count, diff);
 }
 
 DOMString CharacterDataImpl::nodeValue() const
index 7455bc2c62defb63beb7927a7c8a6bdbf2edf7b9..20e1c826bc611b76ab46789f21f52c7864d0bf63 100644 (file)
@@ -226,6 +226,8 @@ public:
     NSImage *snapshotDragImage(DOM::Node node, NSRect *imageRect, NSRect *elementRect) const;
 
     NSFont *fontForCurrentPosition() const;
+    void markMisspellingsInSelection(const DOM::Selection &selection);
+    void updateSpellChecking();
 
     NSFileWrapper *fileWrapperForElement(DOM::ElementImpl *);
     NSAttributedString *attributedString(DOM::NodeImpl *startNode, int startOffset, DOM::NodeImpl *endNode, int endOffset);
index 48c6dceb6f6c04eda5c18f53e91306fb82a41634..46c88b1c77bb0eb9bb1fdf09a288e5de34cdd803 100644 (file)
@@ -939,7 +939,7 @@ QString KWQKHTMLPart::advanceToNextMisspelling()
     WordAwareIterator it(searchRange);
     bool wrapped = false;
     
-    // We go to the end of our first range insted of the start of it, just to be sure
+    // We go to the end of our first range instead 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.
     Node searchEndAfterWrapNode = it.range().endContainer();
@@ -965,11 +965,8 @@ QString KWQKHTMLPart::advanceToNextMisspelling()
                 
                     setSelection(misspellingRange);
                     jumpToSelection();
-#ifndef NDEBUG      // not yet baked enough for deployment
                     // Mark misspelling in document.
                     xmlDocImpl()->addMarker(misspellingRange, DocumentMarker::Spelling);
-#endif
-
                     return result;
                 }
             }
@@ -3643,8 +3640,114 @@ bool KHTMLPart::canRedo() const
     return [[KWQ(this)->_bridge undoManager] canRedo];
 }
 
+void KWQKHTMLPart::markMisspellingsInSelection(const Selection &selection)
+{
+    // No work to do if there is no selection or continuous spell check is off, or the
+    // selection start position is not now rendered (maybe it has been deleted).
+    if (selection.state() == Selection::NONE || 
+        ![_bridge isContinuousSpellCheckingEnabled] || 
+        !selection.start().inRenderedContent())
+        return;
+
+    // Expand selection to word boundaries so that complete words wind up being passed to the spell checker.
+    //
+    // FIXME: It seems that NSSpellChecker is too slow to handle finding multiple mispellings in an
+    // arbitrarily large selection.
+    // So, for now, the idea is to mimic AppKit behavior and limit the selection to the first word 
+    // of the selection passed in.
+    // This is not ideal by any means, but this is the convention.
+    Position end(selection.start().nextWordBoundary());
+    if (end == selection.start())
+        end = end.nextCharacterPosition().nextWordBoundary();
+    Selection s(selection.start().previousWordBoundary(), end);
+    if (s.state() == Selection::NONE)
+        return;
+    // Change to this someday to spell check the entire selection.
+    // The rest of this function is prepared to handle finding multiple misspellings in a 
+    // more-than-one-word selection.
+    //Selection s = Selection(selection.start().previousWordBoundary(), selection.end().nextWordBoundary());
+
+    Range searchRange(s.toRange());
+    
+    // If we're not in an editable node, bail.
+    NodeImpl *editableNodeImpl = searchRange.startContainer().handle();
+    if (!editableNodeImpl->isContentEditable())
+        return;
+    
+    // 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.
+    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())
+        // nothing to search in
+        return;       
+    
+    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
+    WordAwareIterator it(searchRange);
+    
+    while (!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];
+            int startIndex = 0;
+            // Loop over the chunk to find each misspelling in it.
+            while (startIndex < len) {
+                NSRange misspelling = [checker checkSpellingOfString:chunk startingAt:startIndex language:nil wrap:NO inSpellDocumentWithTag:[_bridge spellCheckerDocumentTag] wordCount:NULL];
+                if (misspelling.length == 0) {
+                    break;
+                }
+                else {
+                    // Build up result range and string.  Note the misspelling may span many text nodes,
+                    // but the CharIterator insulates us from this complexity
+                    Range misspellingRange(xmlDocImpl());
+                    CharacterIterator chars(it.range());
+                    chars.advance(misspelling.location);
+                    misspellingRange.setStart(chars.range().startContainer(), chars.range().startOffset());
+                    chars.advance(misspelling.length);
+                    misspellingRange.setEnd(chars.range().startContainer(), chars.range().startOffset());
+                    // Mark misspelling in document.
+                    xmlDocImpl()->addMarker(misspellingRange, DocumentMarker::Spelling);
+                    startIndex = misspelling.location + misspelling.length;
+                }
+            }
+            [chunk release];
+        }
+    
+        it.advance();
+    }
+}
+
+void KWQKHTMLPart::updateSpellChecking()
+{
+    if (xmlDocImpl()) {
+        if ([_bridge isContinuousSpellCheckingEnabled]) {
+            // When continuous spell checking is on, no markers appear in words that
+            // intersect the current selection.
+            Position start(selection().start().previousWordBoundary());
+            Position end(selection().start().nextWordBoundary());
+            if (end == selection().start())
+                end = end.nextCharacterPosition().nextWordBoundary();
+            Selection selection(start, end);
+            xmlDocImpl()->removeMarker(selection.toRange(), DocumentMarker::Spelling);
+        }
+        else {
+            // When continuous spell checking is off, no markers appear after the selection changes.
+            xmlDocImpl()->removeAllMarkers();
+        }
+    }
+}
+
 void KWQKHTMLPart::respondToChangedSelection()
 {
+    updateSpellChecking();
     [_bridge respondToChangedSelection];
 }
 
index 898e555e9221330cbb1af8ecf3b67ae7ab6da34b..b542f0e6de6b7b0b3abd28a5dd821e9bdc8aa641 100644 (file)
@@ -107,6 +107,7 @@ public:
                   const QColor& backgroundColor, QPainter::TextDirection d, bool visuallyOrdered,
                   int letterSpacing, int wordSpacing, bool smallCaps);
     void drawLineForText(int x, int y, int yOffset, int width);
+    void drawLineForMisspelling(int x, int y, int width);
 
     QColor selectedTextBackgroundColor() const;
     void setUsesInactiveTextBackgroundColor(bool u) { _usesInactiveTextBackgroundColor = u; }
index 5d3eff43ae974a232498a98bbc020fe306fe02d1..113df223fb24856c5a61e015c70d98838b815044 100644 (file)
@@ -710,6 +710,14 @@ void QPainter::drawLineForText(int x, int y, int yOffset, int width)
              withColor:data->state.pen.color().getNSColor()];
 }
 
+void QPainter::drawLineForMisspelling(int x, int y, int width)
+{
+    if (data->state.paintingDisabled)
+        return;
+    _updateRenderer();
+    [data->textRenderer drawLineForMisspelling:NSMakePoint(x, y) withWidth:width];
+}
+
 QColor QPainter::selectedTextBackgroundColor() const
 {
     NSColor *color = _usesInactiveTextBackgroundColor ? [NSColor secondarySelectedControlColor] : [NSColor selectedTextBackgroundColor];
index 420d64c5959bc708cd26466244362bde1e6045fc..5595c920a93c9745f4906520a63f4d6175ee6858 100644 (file)
@@ -489,6 +489,7 @@ typedef enum {
 - (void)windowObjectCleared;
 
 - (int)spellCheckerDocumentTag;
+- (BOOL)isContinuousSpellCheckingEnabled;
 
 @end
 
index f3b2c57f7e182bb249ff88e5f23be21c8d966843..cebd57aadb07390e0b51513acd86db75bb6456a7 100644 (file)
@@ -1389,7 +1389,7 @@ static HTMLFormElementImpl *formElementFromDOMElement(DOMElement *element)
         xPos = _part->xPosForVerticalArrowNavigation();
     
     // setting the selection always clears saved vertical navigation x position
-    _part->setSelection(selection);
+    _part->setSelection(selection, true);
     
     // restore vertical navigation x position if necessary
     if (xPos != KHTMLPart::NoXPosForVerticalArrowNavigation)
index b6deaeefb9b090f0878054ce683b6263add85b51..1ee5774a7e187a633addb80eebd3f05d8ee050fb 100644 (file)
@@ -100,6 +100,7 @@ extern void WebCoreInitializeEmptyTextGeometry(WebCoreTextGeometry *geometry);
 - (void)drawRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry;
 - (void)drawHighlightForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry;
 - (void)drawLineForCharacters:(NSPoint)point yOffset:(float)yOffset withWidth:(int)width withColor:(NSColor *)color;
+- (void)drawLineForMisspelling:(NSPoint)point withWidth:(int)width;
 
 // selection point check
 - (int)pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x reversed:(BOOL)reversed includePartialGlyphs:(BOOL)includePartialGlyphs;
index b740dfcf1861e6174b07673e48af6ef633939b30..1e4cfacc2cfc23d36a54924af56a517a294875b8 100644 (file)
@@ -1,3 +1,16 @@
+2004-08-06  Ken Kocienda  <kocienda@apple.com>
+
+        Reviewed by Maciej
+
+        Finish off spellchecking support to HTML editing. Includes work to
+        enable continuous spellchecking.
+
+        * WebCoreSupport.subproj/WebBridge.m:
+        (-[WebBridge isContinuousSpellCheckingEnabled]): Simple bridge method.
+        * WebCoreSupport.subproj/WebTextRenderer.m:
+        (-[WebTextRenderer drawLineForMisspelling:withWidth:]): New method to add
+        AppKit-style misspelling underline.
+
 === Safari-155 ===
 
 2004-08-05  Darin Adler  <darin@apple.com>
index 2fc540c2128ec7a535fa177b201be9cd96c7d050..c86524206715bc0a1b97733471ded510aef1fa2d 100644 (file)
@@ -1342,4 +1342,9 @@ static id <WebFormDelegate> formDelegate(WebBridge *self)
     return [[_frame webView] spellCheckerDocumentTag];
 }
 
+- (BOOL)isContinuousSpellCheckingEnabled
+{
+    return [[_frame webView] isContinuousSpellCheckingEnabled];
+}
+
 @end
index 48e2faaef58d6576819b88f7dd95151cb8767bfe..564400f9343e5654a3f9cc310ef7931aee71a421 100644 (file)
@@ -15,6 +15,7 @@
 #import <WebCore/WebCoreUnicode.h>
 
 #import <WebKit/WebGlyphBuffer.h>
+#import <WebKit/WebGraphicsBridge.h>
 #import <WebKit/WebKitLogging.h>
 #import <WebKit/WebNSObjectExtras.h>
 #import <WebKit/WebTextRendererFactory.h>
@@ -518,6 +519,58 @@ static BOOL alwaysUseATSU = NO;
     }
 }
 
+// Constants for pattern underline
+#define patternWidth 4
+#define patternHeight 3
+
+- (void)drawLineForMisspelling:(NSPoint)point withWidth:(int)width
+{
+    // Constants for pattern color
+    static NSColor *spellingPatternColor = nil;
+    static bool usingDot = false;
+    // Initialize pattern color if needed
+    if (!spellingPatternColor) {
+        NSImage *image = [NSImage imageNamed:@"SpellingDot"];
+        ASSERT(image); // if image is not available, we want to know
+        NSColor *color = (image ? [NSColor colorWithPatternImage:image] : nil);
+        if (color)
+            usingDot = true;
+        else
+            color = [NSColor redColor];
+        spellingPatternColor = [color retain];
+    }
+
+    // Width must be divisible by 4 to make sure we always draw full misspelling dots under words.
+    // Do a small adjustment to shift the underline back to the left if the pattern was
+    // expanded to the right "too much" to accomodate the drawing of a full dot.
+    if (usingDot) {
+        int w = (width + patternWidth) - (width % patternWidth);
+        if (w - width > 2) 
+            point.x -= 1;
+        width = w;
+    }
+
+    // Compute the appropriate phase relative to the top level view in the window.
+    NSPoint originInWindow = [[NSView focusView] convertPoint:point toView:nil];
+    // WebCore may translate the focus, and thus need an extra phase correction
+    NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
+    originInWindow.x += extraPhase.x;
+    originInWindow.y += extraPhase.y;
+    CGSize phase = CGSizeMake(fmodf(originInWindow.x, patternWidth), fmodf(originInWindow.y, patternHeight));
+
+    // Draw underline
+    NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
+    [currentContext saveGraphicsState];
+    [spellingPatternColor set];
+    CGContextSetPatternPhase((CGContextRef)[currentContext graphicsPort], phase);
+    NSRectFillUsingOperation(NSMakeRect(point.x, point.y, width, patternHeight), NSCompositeSourceOver);
+    [currentContext restoreGraphicsState];
+}
+
+#undef patternWidth
+#undef patternHeight
+
 - (int)pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x reversed:(BOOL)reversed includePartialGlyphs:(BOOL)includePartialGlyphs
 {
     if (style->smallCaps && !isSmallCapsRenderer) {