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

kocienda kocienda at 268f45cc-cd09-0410-ab3c-d52691b4dbfc
Sat Sep 26 08:25:28 UTC 2009


The following commit has been merged in the debian/unstable branch:
commit 3dc84cf513b1e4232747aacad839807a914b1c3b
Author: kocienda <kocienda at 268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Date:   Fri Feb 6 00:28:44 2004 +0000

    WebCore:
    
            Reviewed by Hyatt
    
            This patch includes two major pieces of work.
    
            1. The KHTMLSelection object has been reworked to be a value object.
            Part-related state has been moved out to KHTMLPart. This change makes
            it much easier to program with these objects, since they can now be
            treating unambigously like built-in types. As part of this work, I
            did a pass over the API and improved some function names. A related
            part of work was to clean up the relationship between KHTMLSelection,
            KHTMLPart and DOMDocumentImpl, and cleared up how to change the selection
            when that is necessary. (There is now a small set of functions on KHTMLPart).
    
            2. Implemented undo/redo, and hooked into the Cocoa undo architecture. The
            notion of EditCommands has been enhanced by the addition of EditSteps, which
            are primitive operations out of which edit commands are built. Most of the
            existing operations have been updated to use this new design. The last couple
            will be updated soon, so that all supported editing operations will be undoable.
    
            * khtml/editing/htmlediting.cpp:
            * khtml/editing/htmlediting.h:
            * khtml/html/html_elementimpl.cpp:
            (HTMLElementImpl::isFocusable): Prevents children of content editable parents
            from receiving focus.
            * khtml/khtml_part.cpp: Modified to work with new KHTMLSelection class design.
            Also added some new code to work with new edit command architecture.
            * khtml/khtml_part.h:
            * khtml/khtml_selection.cpp: Reworked as a value class. Too much state was
            being stored in this object for it to be convenient to use as a value. That
            has been fixed.
            * khtml/khtml_selection.h:
            * khtml/khtmlpart_p.h:
            (KHTMLPartPrivate::KHTMLPartPrivate):
            (KHTMLPartPrivate::~KHTMLPartPrivate):
            * khtml/khtmlview.cpp: Modified to work with new KHTMLSelection class design.
            * khtml/rendering/render_block.cpp: Modified to work with new KHTMLSelection class design.
            (khtml::RenderBlock::paintObject):
            * khtml/xml/dom_docimpl.cpp:
            (DocumentImpl::updateSelection):
            (DocumentImpl::deleteSelection):
            (DocumentImpl::pasteHTMLString):
            * khtml/xml/dom_docimpl.h:
            * khtml/xml/dom_elementimpl.cpp:
            (ElementImpl::defaultEventHandler): Modified to work with new edit command architecture.
            * kwq/KWQKHTMLPart.h:
            * kwq/KWQKHTMLPart.mm:
            (KWQKHTMLPart::registerCommandForUndo): Added hook to Cocoa undo/redo architecture.
            * kwq/WebCoreBridge.h:
            * kwq/WebCoreBridge.mm:
            (-[WebCoreBridge isSelectionEditable]): Modified to work with new edit command architecture.
            (-[WebCoreBridge moveCaretToPoint:]): Ditto
            (-[WebCoreBridge haveSelection]): Ditto
            (-[WebCoreBridge selectedHTML]): Ditto
            (-[WebCoreBridge setSelectionFrom:startOffset:to:endOffset:]): Ditto
            (-[WebCoreBridge reconstructedSource]): Ditto
            (-[WebCoreBridge undoRedoEditing:]): Added hook to Cocoa undo/redo architecture.
    
    WebKit:
    
            Reviewed by Hyatt
    
    		Added so that editing can hook into Cocoa undo architecture.
    
            * WebCoreSupport.subproj/WebBridge.m:
            (-[WebBridge registerCommandForUndo:]):
    
    
    git-svn-id: http://svn.webkit.org/repository/webkit/trunk@6039 268f45cc-cd09-0410-ab3c-d52691b4dbfc

diff --git a/WebCore/ChangeLog-2005-08-23 b/WebCore/ChangeLog-2005-08-23
index bbbab5d..a27ccca 100644
--- a/WebCore/ChangeLog-2005-08-23
+++ b/WebCore/ChangeLog-2005-08-23
@@ -1,3 +1,62 @@
+2004-02-05  Ken Kocienda  <kocienda at apple.com>
+
+        Reviewed by Hyatt
+        
+        This patch includes two major pieces of work.
+        
+        1. The KHTMLSelection object has been reworked to be a value object.
+        Part-related state has been moved out to KHTMLPart. This change makes
+        it much easier to program with these objects, since they can now be
+        treating unambigously like built-in types. As part of this work, I
+        did a pass over the API and improved some function names. A related
+        part of work was to clean up the relationship between KHTMLSelection, 
+        KHTMLPart and DOMDocumentImpl, and cleared up how to change the selection
+        when that is necessary. (There is now a small set of functions on KHTMLPart). 
+        
+        2. Implemented undo/redo, and hooked into the Cocoa undo architecture. The
+        notion of EditCommands has been enhanced by the addition of EditSteps, which
+        are primitive operations out of which edit commands are built. Most of the
+        existing operations have been updated to use this new design. The last couple
+        will be updated soon, so that all supported editing operations will be undoable.
+
+        * khtml/editing/htmlediting.cpp:
+        * khtml/editing/htmlediting.h:
+        * khtml/html/html_elementimpl.cpp:
+        (HTMLElementImpl::isFocusable): Prevents children of content editable parents
+        from receiving focus.
+        * khtml/khtml_part.cpp: Modified to work with new KHTMLSelection class design.
+        Also added some new code to work with new edit command architecture. 
+        * khtml/khtml_part.h:
+        * khtml/khtml_selection.cpp: Reworked as a value class. Too much state was
+        being stored in this object for it to be convenient to use as a value. That
+        has been fixed.
+        * khtml/khtml_selection.h:
+        * khtml/khtmlpart_p.h:
+        (KHTMLPartPrivate::KHTMLPartPrivate):
+        (KHTMLPartPrivate::~KHTMLPartPrivate):
+        * khtml/khtmlview.cpp: Modified to work with new KHTMLSelection class design.
+        * khtml/rendering/render_block.cpp: Modified to work with new KHTMLSelection class design.
+        (khtml::RenderBlock::paintObject):
+        * khtml/xml/dom_docimpl.cpp:
+        (DocumentImpl::updateSelection):
+        (DocumentImpl::deleteSelection):
+        (DocumentImpl::pasteHTMLString):
+        * khtml/xml/dom_docimpl.h:
+        * khtml/xml/dom_elementimpl.cpp: 
+        (ElementImpl::defaultEventHandler): Modified to work with new edit command architecture.
+        * kwq/KWQKHTMLPart.h:
+        * kwq/KWQKHTMLPart.mm:
+        (KWQKHTMLPart::registerCommandForUndo): Added hook to Cocoa undo/redo architecture.
+        * kwq/WebCoreBridge.h:
+        * kwq/WebCoreBridge.mm:
+        (-[WebCoreBridge isSelectionEditable]): Modified to work with new edit command architecture.
+        (-[WebCoreBridge moveCaretToPoint:]): Ditto
+        (-[WebCoreBridge haveSelection]): Ditto
+        (-[WebCoreBridge selectedHTML]): Ditto
+        (-[WebCoreBridge setSelectionFrom:startOffset:to:endOffset:]): Ditto
+        (-[WebCoreBridge reconstructedSource]): Ditto
+        (-[WebCoreBridge undoRedoEditing:]): Added hook to Cocoa undo/redo architecture.
+
 2004-02-04  David Hyatt  <hyatt at apple.com>
 
 	Improve rule matching in the style system.  Filter out most rules up front, so that only a small
@@ -201,6 +260,7 @@
 
 	Fixed: <rdar://problem/3546924>: REGRESSION: dragging text or images over a WebView is jerky
 
+
         Reviewed by mjs.
 
         * WebCore-combined.exp:
diff --git a/WebCore/khtml/editing/SelectionController.cpp b/WebCore/khtml/editing/SelectionController.cpp
index c631e6d..91e3f1c 100644
--- a/WebCore/khtml/editing/SelectionController.cpp
+++ b/WebCore/khtml/editing/SelectionController.cpp
@@ -61,8 +61,6 @@ using khtml::InlineTextBox;
 using khtml::RenderObject;
 using khtml::RenderText;
 
-enum { CARET_BLINK_FREQUENCY = 500 };
-
 #if APPLE_CHANGES
 static void findWordBoundary(QChar *chars, int len, int position, int *start, int *end);
 static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset);
@@ -71,35 +69,94 @@ static bool startAndEndLineNodesIncludingNode(DOM::NodeImpl *node, int offset, K
 #endif
 
 
-KHTMLSelection::KHTMLSelection() 
-	: QObject(),
-	  m_part(0),
-	  m_baseNode(0), m_baseOffset(0), m_extentNode(0), m_extentOffset(0),
-	  m_startNode(0), m_startOffset(0), m_endNode(0), m_endOffset(0),
-	  m_state(NONE), m_caretBlinkTimer(0),
-      m_baseIsStart(true), m_caretBlinks(true), m_caretPaint(false), 
-      m_visible(false), m_startEndValid(false)
+KHTMLSelection::KHTMLSelection()
+{
+    init();
+}
+
+KHTMLSelection::KHTMLSelection(NodeImpl *node, long offset)
 {
+    init();
+
+	setBaseNode(node);
+	setExtentNode(node);
+	setBaseOffset(offset);
+	setExtentOffset(offset);
+
+    validate();
+}
+
+KHTMLSelection::KHTMLSelection(const DOM::DOMPosition &pos)
+{
+    init();
+
+	setBaseNode(pos.node());
+	setExtentNode(pos.node());
+	setBaseOffset(pos.offset());
+	setExtentOffset(pos.offset());
+
+    validate();
+}
+
+KHTMLSelection::KHTMLSelection(NodeImpl *baseNode, long baseOffset, NodeImpl *endNode, long endOffset)
+{
+    init();
+
+	setBaseNode(baseNode);
+	setExtentNode(endNode);
+	setBaseOffset(baseOffset);
+	setExtentOffset(endOffset);
+
+    validate();
 }
 
 KHTMLSelection::KHTMLSelection(const KHTMLSelection &o)
-	: QObject(),
-	  m_part(o.m_part),
-	  m_baseNode(0), m_baseOffset(0), m_extentNode(0), m_extentOffset(0),
-	  m_startNode(0), m_startOffset(0), m_endNode(0), m_endOffset(0)
 {
-    setBase(o.baseNode(), o.baseOffset());
-    setExtent(o.extentNode(), o.extentOffset());
-    setStart(o.startNode(), o.startOffset());
-    setEnd(o.endNode(), o.endOffset());
-
-	m_state = o.m_state;
-	m_caretBlinkTimer = o.m_caretBlinkTimer;
-	m_baseIsStart = o.m_baseIsStart;
-	m_caretBlinks = o.m_caretBlinks;
-	m_caretPaint = true;
-	m_visible = o.m_visible;
-    m_startEndValid = true;
+    init();
+    
+	setBaseNode(o.baseNode());
+	setExtentNode(o.extentNode());
+	setBaseOffset(o.baseOffset());
+	setExtentOffset(o.extentOffset());
+
+	setStartNode(o.startNode());
+	setEndNode(o.endNode());
+	setStartOffset(o.startOffset());
+	setEndOffset(o.endOffset());
+
+    m_state = o.m_state;
+
+    m_baseIsStart = o.m_baseIsStart;
+    m_needsCaretLayout = o.m_needsCaretLayout;
+
+    // Only copy the coordinates over if the other object
+    // has had a layout, otherwise keep the current
+    // coordinates. This prevents drawing artifacts from
+    // remaining when the caret is painted and then moves,
+    // and the old rectangle needs to be repainted.
+    if (!m_needsCaretLayout) {
+        m_caretX = o.m_caretX;
+        m_caretY = o.m_caretY;
+        m_caretSize = o.m_caretSize;
+    }
+}
+
+void KHTMLSelection::init()
+{
+    m_baseNode = 0;
+    m_baseOffset = 0;
+    m_extentNode = 0; 
+    m_extentOffset = 0;
+    m_startNode = 0;
+    m_startOffset = 0;
+    m_endNode = 0;
+    m_endOffset = 0;
+    m_state = NONE; 
+    m_caretX = 0;
+    m_caretY = 0;
+    m_caretSize = 0;
+    m_baseIsStart = true;
+    m_needsCaretLayout = true;
 }
 
 KHTMLSelection::~KHTMLSelection()
@@ -108,77 +165,74 @@ KHTMLSelection::~KHTMLSelection()
         m_baseNode->deref();
     if (m_extentNode)
         m_extentNode->deref();
+    if (m_startNode)
+        m_startNode->deref();
+    if (m_endNode)
+        m_endNode->deref();
 }
 
 KHTMLSelection &KHTMLSelection::operator=(const KHTMLSelection &o)
 {
-    m_part = o.m_part;
+	setBaseNode(o.baseNode());
+	setExtentNode(o.extentNode());
+	setBaseOffset(o.baseOffset());
+	setExtentOffset(o.extentOffset());
+
+	setStartNode(o.startNode());
+	setEndNode(o.endNode());
+	setStartOffset(o.startOffset());
+	setEndOffset(o.endOffset());
+
+    m_state = o.m_state;
+
+    m_baseIsStart = o.m_baseIsStart;
+    m_needsCaretLayout = o.m_needsCaretLayout;
+    
+    // Only copy the coordinates over if the other object
+    // has had a layout, otherwise keep the current
+    // coordinates. This prevents drawing artifacts from
+    // remaining when the caret is painted and then moves,
+    // and the old rectangle needs to be repainted.
+    if (!m_needsCaretLayout) {
+        m_caretX = o.m_caretX;
+        m_caretY = o.m_caretY;
+        m_caretSize = o.m_caretSize;
+    }
     
-    setBase(o.baseNode(), o.baseOffset());
-    setExtent(o.extentNode(), o.extentOffset());
-    setStart(o.startNode(), o.startOffset());
-    setEnd(o.endNode(), o.endOffset());
-
-	m_state = o.m_state;
-	m_caretBlinkTimer = o.m_caretBlinkTimer;
-	m_baseIsStart = o.m_baseIsStart;
-	m_caretBlinks = o.m_caretBlinks;
-	m_caretPaint = true;
-	m_visible = o.m_visible;
-    m_startEndValid = true;
     return *this;
 }
 
-void KHTMLSelection::setSelection(DOM::NodeImpl *node, long offset)
+void KHTMLSelection::moveTo(DOM::NodeImpl *node, long offset)
 {
-	setBaseNode(node);
-	setExtentNode(node);
-	setBaseOffset(offset);
-	setExtentOffset(offset);
-	update();
-#if EDIT_DEBUG
-    debugPosition();
-#endif
+    moveTo(node, offset, node, offset);
 }
 
-void KHTMLSelection::setSelection(const DOM::Range &r)
+void KHTMLSelection::moveTo(const DOM::Range &r)
 {
-	setSelection(r.startContainer().handle(), r.startOffset(), 
+	moveTo(r.startContainer().handle(), r.startOffset(), 
 		r.endContainer().handle(), r.endOffset());
 }
 
-void KHTMLSelection::setSelection(const DOM::DOMPosition &pos)
+void KHTMLSelection::moveTo(const DOM::DOMPosition &pos)
 {
-	setSelection(pos.node(), pos.offset());
+	moveTo(pos.node(), pos.offset());
 }
 
-void KHTMLSelection::setSelection(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset)
+void KHTMLSelection::moveTo(const KHTMLSelection &o)
+{
+	moveTo(o.baseNode(), o.baseOffset(), o.extentNode(), o.extentOffset());
+}
+
+void KHTMLSelection::moveTo(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset)
 {
 	setBaseNode(baseNode);
 	setExtentNode(extentNode);
 	setBaseOffset(baseOffset);
 	setExtentOffset(extentOffset);
-	update();
-#if EDIT_DEBUG
-    debugPosition();
-#endif
-}
-
-void KHTMLSelection::setBase(DOM::NodeImpl *node, long offset)
-{
-	setBaseNode(node);
-	setBaseOffset(offset);
-	update();
+	validate();
 }
 
-void KHTMLSelection::setExtent(DOM::NodeImpl *node, long offset)
-{
-	setExtentNode(node);
-	setExtentOffset(offset);
-	update();
-}
-
-bool KHTMLSelection::alterSelection(EAlter alter, EDirection dir, ETextElement elem)
+bool KHTMLSelection::modify(EAlter alter, EDirection dir, ETextElement elem)
 {
     DOMPosition pos;
     
@@ -211,187 +265,137 @@ bool KHTMLSelection::alterSelection(EAlter alter, EDirection dir, ETextElement e
         return false;
     
     if (alter == MOVE)
-        setSelection(pos.node(), pos.offset());
+        moveTo(pos.node(), pos.offset());
     else // alter == EXTEND
         setExtent(pos.node(), pos.offset());
     
     return true;
 }
 
-void KHTMLSelection::clearSelection()
+void KHTMLSelection::expandToElement(ETextElement select)
+{
+    validate(select);
+}
+
+void KHTMLSelection::clear()
 {
 	setBaseNode(0);
 	setExtentNode(0);
 	setBaseOffset(0);
 	setExtentOffset(0);
-	update();
-}
-
-NodeImpl *KHTMLSelection::startNode() const
-{ 
-    return m_startNode;
+	validate();
 }
 
-long KHTMLSelection::startOffset() const
-{ 
-    return m_startOffset;
+void KHTMLSelection::setBase(DOM::NodeImpl *node, long offset)
+{
+	setBaseNode(node);
+	setBaseOffset(offset);
+	validate();
 }
 
-NodeImpl *KHTMLSelection::endNode() const 
+void KHTMLSelection::setExtent(DOM::NodeImpl *node, long offset)
 {
-    return m_endNode;
+	setExtentNode(node);
+	setExtentOffset(offset);
+	validate();
 }
 
-long KHTMLSelection::endOffset() const 
-{ 
-    return m_endOffset;
+void KHTMLSelection::setNeedsLayout(bool flag)
+{
+    m_needsCaretLayout = flag;
 }
 
-void KHTMLSelection::setVisible(bool flag)
+bool KHTMLSelection::isEmpty() const
 {
-    m_visible = flag;
-    update();
+    return m_baseNode == 0 && m_extentNode == 0;
 }
 
-void KHTMLSelection::invalidate()
+Range KHTMLSelection::toRange() const
 {
-    update();
+    if (isEmpty())
+        return Range();
+
+    return Range(Node(startNode()), startOffset(), Node(endNode()), endOffset());
 }
 
-void KHTMLSelection::update()
+void KHTMLSelection::layoutCaret()
 {
-    // make sure we do not have a dangling start or end
-	if (!m_baseNode && !m_extentNode) {
-        setBaseOffset(0);
-        setExtentOffset(0);
-        m_baseIsStart = true;
+    if (isEmpty() || !startNode()->renderer()) {
+        m_caretX = m_caretY = m_caretSize = 0;
     }
-	else if (!m_baseNode) {
-		setBaseNode(m_extentNode);
-		setBaseOffset(m_extentOffset);
-        m_baseIsStart = true;
-	}
-	else if (!m_extentNode) {
-		setExtentNode(m_baseNode);
-		setExtentOffset(m_baseOffset);
-        m_baseIsStart = true;
-	}
     else {
-        // adjust m_baseIsStart as needed
-        if (m_baseNode == m_extentNode) {
-            if (m_baseOffset > m_extentOffset)
-                m_baseIsStart = false;
-            else 
-                m_baseIsStart = true;
-        }
-        else if (nodeIsBeforeNode(m_baseNode, m_extentNode))
-            m_baseIsStart = true;
-        else
-            m_baseIsStart = false;
+        int w;
+        startNode()->renderer()->caretPos(startOffset(), true, m_caretX, m_caretY, w, m_caretSize);
     }
 
-    // update start and end
-    m_startEndValid = false;
-    calculateStartAndEnd();
-    
-    // update the blink timer
-    if (m_caretBlinkTimer >= 0)
-        killTimer(m_caretBlinkTimer);
-    if (m_visible && m_state == CARET && m_caretBlinks)
-        m_caretBlinkTimer = startTimer(CARET_BLINK_FREQUENCY);
-    else
-        m_caretBlinkTimer = -1;
+    m_needsCaretLayout = false;
+}
 
-    // short-circuit if not visible
-    if (!m_visible) {
-        if (m_caretPaint) {
-            m_caretPaint = false;
-            repaint();
-        }
-        return;
-    }
+QRect KHTMLSelection::getRepaintRect()
+{
+    // EDIT FIXME: fudge a bit to make sure we don't leave behind artifacts
+    return QRect(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2);
+}
 
-    // short-circuit if not CARET state
-	if (m_state != CARET)
-		return;
+void KHTMLSelection::needsCaretRepaint()
+{
+    if (isEmpty())
+        return;
 
-    // calculate the new caret rendering position
-    int oldX = m_caretX;   
-    int oldY = m_caretY;   
-    int oldSize = m_caretSize;
-    
-    int newX = 0;
-    int newY = 0;
-    int newSize = 0;
-    
-    NodeImpl *node = startNode();
-    if (node && node->renderer()) {
-        int w;
-        node->renderer()->caretPos(startOffset(), true, newX, newY, w, newSize);
-    }
+    if (!startNode()->getDocument())
+        return;
 
-    // repaint the old position if necessary
-    // prevents two carets from ever being drawn
-    if (m_caretPaint && (oldX != newX || oldY != newY || oldSize != newSize)) {
-        repaint();
-    }
+    KHTMLView *v = startNode()->getDocument()->view();
+    if (!v)
+        return;
 
-    // update caret rendering position
-    m_caretX = newX;
-    m_caretY = newY;
-    m_caretSize = newSize;
-    
-    // paint the caret if it is visible
-    if (m_visible && m_caretSize != 0) {
-        m_caretPaint = true;
-        repaint();
+    if (m_needsCaretLayout) {
+        // repaint old position and calculate new position
+        v->updateContents(getRepaintRect(), false);
+        layoutCaret();
+        
+        // EDIT FIXME: This is an unfortunate hack.
+        // Basically, we can't trust this layout position since we 
+        // can't guarantee that the check to see if we are in unrendered 
+        // content will work at this point. We may have to wait for
+        // a layout and re-render of the document to happen. So, resetting this
+        // flag will cause another caret layout to happen the first time
+        // that we try to paint the caret after this call. That one will work since
+        // it happens after the document has accounted for any editing
+        // changes which may have been done.
+        // And, we need to leave this layout here so the caret moves right 
+        // away after clicking.
+        m_needsCaretLayout = true;
     }
+    v->updateContents(getRepaintRect(), false);
 }
 
-bool KHTMLSelection::isEmpty() const
+void KHTMLSelection::paintCaret(QPainter *p, const QRect &rect)
 {
-    return m_baseNode == 0 && m_extentNode == 0;
-}
+    if (isEmpty())
+        return;
 
-#ifdef APPLE_CHANGES
-void KHTMLSelection::paint(QPainter *p, const QRect &rect) const
-{
-    if (!m_caretPaint || m_state != CARET)
+    if (m_state != CARET)
         return;
 
-    QRect pos(m_caretX, m_caretY, 1, m_caretSize);
-    if (pos.intersects(rect)) {
+    if (m_needsCaretLayout) {
+        DOMPosition pos = DOMPosition(startNode(), startOffset());
+        if (!inRenderedContent(pos)) {
+            moveToRenderedContent();
+        }
+        layoutCaret();
+    }
+
+    QRect caretRect(m_caretX, m_caretY, 1, m_caretSize);
+    if (caretRect.intersects(rect)) {
         QPen pen = p->pen();
-        pen.setStyle(SolidLine);
+        pen.setStyle(Qt::SolidLine);
         pen.setColor(Qt::black);
         pen.setWidth(1);
         p->setPen(pen);
-        p->drawLine(pos.left(), pos.top(), pos.left(), pos.bottom());
+        p->drawLine(caretRect.left(), caretRect.top(), caretRect.left(), caretRect.bottom());
     }
 }
-#endif
-
-void KHTMLSelection::setPart(KHTMLPart *part)
-{
-    m_part = part;
-}
-
-void KHTMLSelection::timerEvent(QTimerEvent *e)
-{
-    if (e->timerId() == m_caretBlinkTimer && m_visible) {
-        m_caretPaint = !m_caretPaint;
-        repaint();
-    }
-}
-
-void KHTMLSelection::repaint(bool immediate) const
-{
-    KHTMLView *v = m_part->view();
-    if (!v)
-        return;
-    // EDIT FIXME: fudge a bit to make sure we don't leave behind artifacts
-    v->updateContents(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2, immediate);
-}
 
 void KHTMLSelection::setBaseNode(DOM::NodeImpl *node)
 {
@@ -431,12 +435,6 @@ void KHTMLSelection::setExtentOffset(long offset)
 	m_extentOffset = offset;
 }
 
-void KHTMLSelection::setStart(DOM::NodeImpl *node, long offset)
-{
-    setStartNode(node);
-    setStartOffset(offset);
-}
-
 void KHTMLSelection::setStartNode(DOM::NodeImpl *node)
 {
 	if (m_startNode == node)
@@ -456,12 +454,6 @@ void KHTMLSelection::setStartOffset(long offset)
 	m_startOffset = offset;
 }
 
-void KHTMLSelection::setEnd(DOM::NodeImpl *node, long offset)
-{
-    setEndNode(node);
-    setEndOffset(offset);
-}
-
 void KHTMLSelection::setEndNode(DOM::NodeImpl *node)
 {
 	if (m_endNode == node)
@@ -481,17 +473,39 @@ void KHTMLSelection::setEndOffset(long offset)
 	m_endOffset = offset;
 }
 
-void KHTMLSelection::expandSelection(ETextElement select)
-{
-    m_startEndValid = false;
-    calculateStartAndEnd(select);
-}
-
-void KHTMLSelection::calculateStartAndEnd(ETextElement select)
+void KHTMLSelection::validate(ETextElement expandTo)
 {
-    if (m_startEndValid)
-        return;
+    // make sure we do not have a dangling start or end
+	if (!m_baseNode && !m_extentNode) {
+        setBaseOffset(0);
+        setExtentOffset(0);
+        m_baseIsStart = true;
+    }
+	else if (!m_baseNode) {
+		setBaseNode(m_extentNode);
+		setBaseOffset(m_extentOffset);
+        m_baseIsStart = true;
+	}
+	else if (!m_extentNode) {
+		setExtentNode(m_baseNode);
+		setExtentOffset(m_baseOffset);
+        m_baseIsStart = true;
+	}
+    else {
+        // adjust m_baseIsStart as needed
+        if (m_baseNode == m_extentNode) {
+            if (m_baseOffset > m_extentOffset)
+                m_baseIsStart = false;
+            else 
+                m_baseIsStart = true;
+        }
+        else if (nodeIsBeforeNode(m_baseNode, m_extentNode))
+            m_baseIsStart = true;
+        else
+            m_baseIsStart = false;
+    }
 
+    // calculate the correct start and end positions
 #if !APPLE_CHANGES
     if (m_baseIsStart) {
         setStartNode(m_baseNode);
@@ -506,7 +520,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
         setEndOffset(m_baseOffset);
     }
 #else
-    if (select == CHARACTER) {
+    if (expandTo == CHARACTER) {
         if (m_baseIsStart) {
             setStartNode(m_baseNode);
             setStartOffset(m_baseOffset);
@@ -520,7 +534,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
             setEndOffset(m_baseOffset);
         }
     }
-    else if (select == WORD) {
+    else if (expandTo == WORD) {
         int baseStartOffset = m_baseOffset;
         int baseEndOffset = m_baseOffset;
         int extentStartOffset = m_extentOffset;
@@ -550,7 +564,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
             setEndOffset(baseEndOffset);
         }
     }
-    else {  // select == LINE
+    else {  // expandTo == LINE
         KHTMLSelection baseSelection = *this;
         KHTMLSelection extentSelection = *this;
         if (m_baseNode && (m_baseNode->nodeType() == Node::TEXT_NODE || m_baseNode->nodeType() == Node::CDATA_SECTION_NODE)) {
@@ -584,24 +598,33 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
     }
 #endif  // APPLE_CHANGES
 
-	// update the state
+	// adjust the state
 	if (!m_startNode && !m_endNode)
 		m_state = NONE;
-	if (m_startNode == m_endNode && m_startOffset == m_endOffset)
+	else if (m_startNode == m_endNode && m_startOffset == m_endOffset)
 		m_state = CARET;
 	else
 		m_state = RANGE;
+
+    m_needsCaretLayout = true;
     
-    m_startEndValid = true;
+#if EDIT_DEBUG
+    debugPosition();
+#endif
 }
 
-DOMPosition KHTMLSelection::previousCharacterPosition()
+DOMPosition KHTMLSelection::previousCharacterPosition() const
 {
-    if (!startNode())
-        return DOMPosition();
+    return previousCharacterPosition(DOMPosition(startNode(), startOffset()));
+}
 
-	NodeImpl *node = startNode();
-	long offset = startOffset() - 1;
+DOMPosition KHTMLSelection::previousCharacterPosition(const DOMPosition &from)
+{
+    if (!from.node())
+        return from;
+
+	NodeImpl *node = from.node();
+	long offset = from.offset() - 1;
 
     //
     // Look in this renderer
@@ -653,7 +676,7 @@ DOMPosition KHTMLSelection::previousCharacterPosition()
     			 	continue;
     		}
             offset = renderer->caretMaxOffset();
-            if (!renderer->precedesLineBreak())
+            if (renderer->nextEditable() && !renderer->precedesLineBreak())
                 offset--;
             assert(offset >= 0);
             return DOMPosition(renderer->element(), offset);
@@ -662,16 +685,22 @@ DOMPosition KHTMLSelection::previousCharacterPosition()
     }
 
     // can't move the position
-    return DOMPosition(startNode(), startOffset());
+    return from;
+}
+
+
+DOMPosition KHTMLSelection::nextCharacterPosition() const
+{
+    return nextCharacterPosition(DOMPosition(endNode(), endOffset()));
 }
 
-DOMPosition KHTMLSelection::nextCharacterPosition()
+DOMPosition KHTMLSelection::nextCharacterPosition(const DOMPosition &from)
 {
-    if (!endNode())
+    if (!from.node())
         return DOMPosition();
 
-	NodeImpl *node = endNode();
-	long offset = endOffset() + 1;
+ 	NodeImpl *node = from.node();
+ 	long offset = from.offset() + 1;
 
     //
     // Look in this renderer
@@ -746,9 +775,70 @@ DOMPosition KHTMLSelection::nextCharacterPosition()
     }
 
     // can't move the position
-    return DOMPosition(endNode(), endOffset());
+    return from;
 }
 
+bool KHTMLSelection::moveToRenderedContent()
+{
+    if (isEmpty())
+        return false;
+        
+    if (m_state != CARET)
+        return false;
+
+    DOMPosition pos = DOMPosition(startNode(), startOffset());
+    if (inRenderedContent(pos))
+        return true;
+        
+    // not currently rendered, try moving to next
+    DOMPosition next = nextCharacterPosition(pos);
+    if (next != pos) {
+        moveTo(next);
+        return true;
+    }
+
+    // could not be moved to next, try prev
+    DOMPosition prev = previousCharacterPosition(pos);
+    if (prev != pos) {
+        moveTo(prev);
+        return true;
+    }
+    
+    return false;
+}
+
+bool KHTMLSelection::inRenderedContent(const DOMPosition &pos)
+{
+    if (pos.isEmpty())
+        return false;
+        
+ 	long offset = pos.offset();
+
+    RenderObject *renderer = pos.node()->renderer();
+    if (!renderer)
+        return false;
+    
+    if (renderer->isText() && !renderer->isBR()) {
+        RenderText *textRenderer = static_cast<khtml::RenderText *>(renderer);
+        for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+            if (offset >= box->m_start && offset <= box->m_start + box->m_len) {
+                return true;
+            }
+            else if (offset < box->m_start) {
+                // The offset we're looking for is before this node
+                // this means the offset must be in content that is
+                // not rendered. Return false.
+                return false;
+            }
+        }
+    }
+    else if (offset >= renderer->caretMinOffset() && offset <= renderer->caretMaxOffset()) {
+        return true;
+    }
+    
+    return false;
+}
+ 
 bool KHTMLSelection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2) 
 {
 	if (!n1 || !n2) 
@@ -787,7 +877,7 @@ bool KHTMLSelection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2)
         n2 = n2->parentNode();
     }
     // Iterate through the parent's children until n1 or n2 is found
-    n = n1->parentNode()->firstChild();
+    n = n1->parentNode() ? n1->parentNode()->firstChild() : n1->firstChild();
     while (n) {
         if (n == n1) {
             result = true;
@@ -946,7 +1036,7 @@ static bool startAndEndLineNodesIncludingNode(DOM::NodeImpl *node, int offset, K
         if (!lastRunAt (renderNode, selectionPointY, endNode, endOffset))
             return false;
         
-        selection.setSelection(startNode, startOffset, endNode, endOffset);
+        selection.moveTo(startNode, startOffset, endNode, endOffset);
         
         return true;
     }
@@ -1010,7 +1100,7 @@ void KHTMLSelection::debugRenderer(RenderObject *r, bool selected) const
             
             show = show.replace("\n", " ");
             show = show.replace("\r", " ");
-            fprintf(stderr, "==> #text : %s at %d\n", show.latin1(), pos);
+            fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.latin1(), pos);
             fprintf(stderr, "           ");
             for (int i = 0; i < caret; i++)
                 fprintf(stderr, " ");
@@ -1021,7 +1111,7 @@ void KHTMLSelection::debugRenderer(RenderObject *r, bool selected) const
                 text = text.left(max - 3) + "...";
             else
                 text = text.left(max);
-            fprintf(stderr, "    #text : %s\n", text.latin1());
+            fprintf(stderr, "    #text : \"%s\"\n", text.latin1());
         }
     }
 }
diff --git a/WebCore/khtml/editing/SelectionController.h b/WebCore/khtml/editing/SelectionController.h
index e888924..6932b21 100644
--- a/WebCore/khtml/editing/SelectionController.h
+++ b/WebCore/khtml/editing/SelectionController.h
@@ -26,14 +26,9 @@
 #ifndef __khtml_selection_h__
 #define __khtml_selection_h__
 
-#include <qobject.h>
-
 class KHTMLPart;
-class KHTMLPartPrivate;
-class KHTMLView;
 class QPainter;
 class QRect;
-class QTimerEvent;
 
 namespace DOM {
     class DOMPosition;
@@ -45,12 +40,13 @@ namespace khtml {
     class RenderObject;
 }
 
-class KHTMLSelection : public QObject
+class KHTMLSelection
 {
-  Q_OBJECT
-
 public:
     KHTMLSelection();
+    KHTMLSelection(DOM::NodeImpl *node, long offset);
+    KHTMLSelection(const DOM::DOMPosition &);
+    KHTMLSelection(DOM::NodeImpl *startNode, long startOffset, DOM::NodeImpl *endNode, long endOffset);
     KHTMLSelection(const KHTMLSelection &);
     ~KHTMLSelection();
 
@@ -61,41 +57,45 @@ public:
 
 	EState state() const { return m_state; }
 
-    void setSelection(DOM::NodeImpl *node, long offset);
-    void setSelection(const DOM::Range &);
-    void setSelection(const DOM::DOMPosition &);
-    void setSelection(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset);
+    void moveTo(DOM::NodeImpl *node, long offset);
+    void moveTo(const DOM::Range &);
+    void moveTo(const DOM::DOMPosition &);
+    void moveTo(const KHTMLSelection &);
+    void moveTo(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset);
+    bool modify(EAlter, EDirection, ETextElement);
+    void expandToElement(ETextElement);
+    void clear();
+
+    bool moveToRenderedContent();
+    
     void setBase(DOM::NodeImpl *node, long offset);
     void setExtent(DOM::NodeImpl *node, long offset);
-    void expandSelection(ETextElement);
-    bool alterSelection(EAlter, EDirection, ETextElement);
-    void clearSelection();
-    
+
     DOM::NodeImpl *baseNode() const { return m_baseNode; }
     long baseOffset() const { return m_baseOffset; }
 
     DOM::NodeImpl *extentNode() const { return m_extentNode; }
     long extentOffset() const { return m_extentOffset; }
 
-    DOM::NodeImpl *startNode() const;
-    long startOffset() const;
+    DOM::NodeImpl *startNode() const { return m_startNode; }
+    long startOffset() const { return m_startOffset; }
 
-    DOM::NodeImpl *endNode() const;
-    long endOffset() const;
+    DOM::NodeImpl *endNode() const { return m_endNode; }
+    long endOffset() const { return m_endOffset; }
 
-    void setVisible(bool flag=true);
-    bool visible() const { return m_visible; }
-
-    DOM::DOMPosition previousCharacterPosition();
-    DOM::DOMPosition nextCharacterPosition();
+    DOM::DOMPosition previousCharacterPosition() const;
+    static DOM::DOMPosition previousCharacterPosition(const DOM::DOMPosition &from);
+    DOM::DOMPosition nextCharacterPosition() const;
+    static DOM::DOMPosition nextCharacterPosition(const DOM::DOMPosition &from);
         
-    void invalidate();
+    void setNeedsLayout(bool flag=true);
     
     bool isEmpty() const;
+    DOM::Range toRange() const;
+
     
-#ifdef APPLE_CHANGES
-    void paint(QPainter *p, const QRect &rect) const;
-#endif
+    void debugPosition() const;
+    void debugRenderer(khtml::RenderObject *r, bool selected) const;
 
     KHTMLSelection &operator=(const KHTMLSelection &o);
     
@@ -104,35 +104,30 @@ public:
     
     friend class KHTMLPart;
 
-    void debugPosition() const;
-    void debugRenderer(khtml::RenderObject *r, bool selected) const;
-
 private:
-    void setPart(KHTMLPart *part);
-
-    void update();
+    void init();
+    void validate(ETextElement expandTo=CHARACTER);
 
-    void timerEvent(QTimerEvent *e);
-    void repaint(bool immediate=false) const;
+    void layoutCaret();
+    void needsCaretRepaint();
+    QRect getRepaintRect();
+    void paintCaret(QPainter *p, const QRect &rect);
 
 	void setBaseNode(DOM::NodeImpl *);
 	void setBaseOffset(long);
 	void setExtentNode(DOM::NodeImpl *);
 	void setExtentOffset(long);
 
-	void setStart(DOM::NodeImpl *, long);
 	void setStartNode(DOM::NodeImpl *);
 	void setStartOffset(long);
-	void setEnd(DOM::NodeImpl *, long);
 	void setEndNode(DOM::NodeImpl *);
 	void setEndOffset(long);
 
+    bool inRenderedContent(const DOM::DOMPosition &);
     bool nodeIsBeforeNode(DOM::NodeImpl *n1, DOM::NodeImpl *n2);
 
     void calculateStartAndEnd(ETextElement select=CHARACTER);
     
-    KHTMLPart *m_part;            // part for this selection
-
     DOM::NodeImpl *m_baseNode;    // base node for the selection
     long m_baseOffset;            // offset into base node where selection is
     DOM::NodeImpl *m_extentNode;  // extent node for the selection
@@ -145,24 +140,19 @@ private:
 
 	EState m_state;               // the state of the selection
 
-    int m_caretBlinkTimer;        // caret blink frequency timer id
-	
 	int m_caretX;
 	int m_caretY;
 	int m_caretSize;
 
-	bool m_baseIsStart : 1;     // true if base node is before the extent node
-    bool m_caretBlinks : 1;     // true if caret blinks
-    bool m_caretPaint : 1;      // flag used to deal with blinking the caret
-    bool m_visible : 1;         // true if selection is to be displayed at all
-	bool m_startEndValid : 1;   // true if the start and end are valid
+	bool m_baseIsStart : 1;       // true if base node is before the extent node
+	bool m_needsCaretLayout : 1;  // true if the caret position needs to be calculated
 };
 
 
 inline bool operator==(const KHTMLSelection &a, const KHTMLSelection &b)
 {
-    return a.baseNode() == b.baseNode() && a.baseOffset() == b.baseOffset() &&
-        a.extentNode() == b.extentNode() && a.extentOffset() == b.extentOffset();
+    return a.startNode() == b.startNode() && a.startOffset() == b.startOffset() &&
+        a.endNode() == b.endNode() && a.endOffset() == b.endOffset();
 }
 
 inline bool operator!=(const KHTMLSelection &a, const KHTMLSelection &b)
diff --git a/WebCore/khtml/editing/htmlediting.cpp b/WebCore/khtml/editing/htmlediting.cpp
index 563e7b0..3b84452 100644
--- a/WebCore/khtml/editing/htmlediting.cpp
+++ b/WebCore/khtml/editing/htmlediting.cpp
@@ -40,61 +40,790 @@
 #include "xml/dom_textimpl.h"
 
 using DOM::DocumentImpl;
+using DOM::DOMException;
+using DOM::DOMPosition;
 using DOM::DOMString;
 using DOM::ElementImpl;
 using DOM::Node;
 using DOM::NodeImpl;
+using DOM::NodeListImpl;
 using DOM::Range;
 using DOM::RangeImpl;
 using DOM::TextImpl;
 
+using khtml::AppendNodeStep;
+using khtml::CompositeEditStep;
+using khtml::DeleteSelectionStep;
+using khtml::DeleteTextStep;
+using khtml::EditStep;
+using khtml::InsertNodeBeforeStep;
+using khtml::InsertTextStep;
+using khtml::JoinTextNodesStep;
+using khtml::ModifyTextNodeStep;
+using khtml::RemoveNodeStep;
+using khtml::SetSelectionStep;
+using khtml::SplitTextNodeStep;
+
 using khtml::DeleteTextCommand;
 using khtml::EditCommand;
-using khtml::EditCommandID;
 using khtml::InputTextCommand;
 
+#define APPLY_STEP(s) do { \
+        int result = s->apply(); \
+        if (result) { \
+            return result; \
+        } \
+        m_steps.append(s); \
+    } while (0)
+
 //------------------------------------------------------------------------------------------
-// EditCommand
 
-EditCommand::EditCommand(DOM::DocumentImpl *document) : m_document(0)
+#pragma mark EditSteps
+
+//------------------------------------------------------------------------------------------
+// EditStep
+
+EditStep::EditStep(DocumentImpl *document) : m_document(document), m_state(NOT_APPLIED)
 {
-    m_document = document;
-    if (m_document) {
-        m_document->ref();
+    assert(m_document);
+    assert(m_document->part());
+    m_document->ref();
+    m_startingSelection = m_document->part()->selection();
+    m_endingSelection = m_startingSelection;
+}
+
+EditStep::~EditStep()
+{
+    assert(m_document);
+    m_document->deref();
+}
+
+int EditStep::apply()
+{
+    assert(m_document);
+    assert(m_document->part());
+
+    m_state = APPLIED;
+    m_document->part()->setSelection(m_endingSelection);
+    return EditResultOK;
+}
+
+int EditStep::unapply()
+{
+    assert(m_document);
+    assert(m_document->part());
+
+    m_state = NOT_APPLIED;
+    m_document->part()->setSelection(m_startingSelection);
+    return EditResultOK;
+}
+
+int EditStep::reapply()
+{
+    return apply();
+}
+
+//------------------------------------------------------------------------------------------
+// CompositeEditStep
+
+CompositeEditStep::CompositeEditStep(DocumentImpl *document) 
+    : EditStep(document)
+{
+    m_steps.setAutoDelete(true);
+}
+
+CompositeEditStep::~CompositeEditStep()
+{
+}
+
+int CompositeEditStep::unapply()
+{
+    QPtrListIterator<EditStep> it(m_steps);
+    for (it.toLast(); it.current(); --it) {
+        int result = it.current()->unapply();
+        if (result != EditResultOK)
+            return result;
     }
+
+    return EditStep::unapply();
 }
 
-EditCommand::~EditCommand()
+int CompositeEditStep::reapply()
+{
+    QPtrListIterator<EditStep> it(m_steps);
+    for (; it.current(); ++it) {
+        int result = it.current()->reapply();
+        if (result != EditResultOK)
+            return result;
+    }
+
+    // Calls apply() and not reapply(), given that the default implementation of
+    // EditStep::reapply() calls apply(), which dispatches virtually.
+    return EditStep::apply();
+}
+
+//------------------------------------------------------------------------------------------
+// InsertNodeBeforeStep
+
+InsertNodeBeforeStep::InsertNodeBeforeStep(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
+    : EditStep(document), m_insertChild(insertChild), m_refChild(refChild)
+{
+    assert(m_insertChild);
+    m_insertChild->ref();
+
+    assert(m_refChild);
+    m_refChild->ref();
+}
+
+InsertNodeBeforeStep::~InsertNodeBeforeStep()
+{
+    if (m_insertChild)
+        m_insertChild->deref();
+    if (m_refChild)
+        m_refChild->deref();
+}
+
+int InsertNodeBeforeStep::apply()
+{
+    assert(m_insertChild);
+    assert(m_refChild);
+    assert(m_refChild->parent());
+    assert(state() == NOT_APPLIED);
+
+    int exceptionCode;
+    m_refChild->parent()->insertBefore(m_insertChild, m_refChild, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    return EditStep::apply();
+}
+
+int InsertNodeBeforeStep::unapply()
+{
+    assert(m_insertChild);
+    assert(m_refChild);
+    assert(m_refChild->parent());
+    assert(state() == APPLIED);
+
+    int exceptionCode;
+    m_refChild->parent()->removeChild(m_insertChild, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    return EditStep::unapply();
+}
+
+//------------------------------------------------------------------------------------------
+// AppendNodeStep
+
+AppendNodeStep::AppendNodeStep(DocumentImpl *document, NodeImpl *parent, NodeImpl *appendChild)
+    : EditStep(document), m_parent(parent), m_appendChild(appendChild)
+{
+    assert(m_parent);
+    m_parent->ref();
+
+    assert(m_appendChild);
+    m_appendChild->ref();
+}
+
+AppendNodeStep::~AppendNodeStep()
+{
+    if (m_parent)
+        m_parent->deref();
+    if (m_appendChild)
+        m_appendChild->deref();
+}
+
+int AppendNodeStep::apply()
+{
+    assert(m_parent);
+    assert(m_appendChild);
+    assert(state() == NOT_APPLIED);
+
+    int exceptionCode;
+    m_parent->appendChild(m_appendChild, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    return EditStep::apply();
+}
+
+int AppendNodeStep::unapply()
 {
-    if (m_document)
-        m_document->deref();
+    assert(m_parent);
+    assert(m_appendChild);
+    assert(state() == APPLIED);
+
+    int exceptionCode;
+    m_parent->removeChild(m_appendChild, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    return EditStep::unapply();
+}
+
+//------------------------------------------------------------------------------------------
+// RemoveNodeStep
+
+RemoveNodeStep::RemoveNodeStep(DocumentImpl *document, NodeImpl *removeChild)
+    : EditStep(document), m_parent(0), m_removeChild(removeChild), m_refChild(0)
+{
+    assert(m_removeChild);
+    m_removeChild->ref();
+
+    m_parent = m_removeChild->parentNode();
+    assert(m_parent);
+    m_parent->ref();
+    
+    NodeListImpl *children = m_parent->childNodes();
+    for (int i = children->length(); i >= 0; i--) {
+        NodeImpl *node = children->item(i);
+        if (node == m_removeChild)
+            break;
+        m_refChild = node;
+    }
+    
+    if (m_refChild)
+        m_refChild->ref();
+}
+
+RemoveNodeStep::~RemoveNodeStep()
+{
+    if (m_parent)
+        m_parent->deref();
+    if (m_removeChild)
+        m_removeChild->deref();
+    if (m_refChild)
+        m_refChild->deref();
+}
+
+int RemoveNodeStep::apply()
+{
+    assert(m_parent);
+    assert(m_removeChild);
+    assert(state() == NOT_APPLIED);
+
+    int exceptionCode;
+    m_parent->removeChild(m_removeChild, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    return EditStep::apply();
+}
+
+int RemoveNodeStep::unapply()
+{
+    assert(m_parent);
+    assert(m_removeChild);
+    assert(state() == APPLIED);
+
+    int exceptionCode;
+    if (m_refChild)
+        m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
+    else
+        m_parent->appendChild(m_removeChild, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    return EditStep::unapply();
+}
+
+//------------------------------------------------------------------------------------------
+// ModifyTextNodeStep
+
+ModifyTextNodeStep::ModifyTextNodeStep(DocumentImpl *document, TextImpl *text, long offset)
+    : EditStep(document), m_text1(0), m_text2(text), m_offset(offset)
+{
+    assert(m_text2);
+    assert(m_text2->length() > 0);
+    assert(m_offset >= 0);
+
+    m_text2->ref();
+}
+
+ModifyTextNodeStep::ModifyTextNodeStep(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
+    : EditStep(document), m_text1(text1), m_text2(text2), m_offset(0)
+{
+    assert(m_text1);
+    assert(m_text2);
+    assert(m_text1->nextSibling() == m_text2);
+    assert(m_text1->length() > 0);
+    assert(m_text2->length() > 0);
+
+    m_text1->ref();
+    m_text2->ref();
+}
+
+ModifyTextNodeStep::~ModifyTextNodeStep()
+{
+    if (m_text2)
+        m_text2->deref();
+    if (m_text1)
+        m_text1->deref();
 }
 
-void EditCommand::deleteSelection()
+int ModifyTextNodeStep::splitTextNode()
 {
-    if (!m_document || !m_document->view() || !m_document->view()->part())
-        return;
+    assert(m_text2);
+    assert(m_text1 == 0);
+    assert(m_offset > 0);
+    assert(state() == splitState());
+
+    RenderObject *textRenderer = m_text2->renderer();
+    if (!textRenderer)
+        return EditResultFailed;
+
+    if (m_offset <= textRenderer->caretMinOffset() || m_offset >= textRenderer->caretMaxOffset())
+        return EditResultNoActionTaken;
+
+    int exceptionCode;
+    TextImpl *m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode));
+    if (exceptionCode)
+        return exceptionCode;
+    assert(m_text1);
+    m_text1->ref();
+
+    m_text2->deleteData(0, m_offset, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+        
+    assert(m_text2->previousSibling()->isTextNode());
+    m_text1 = static_cast<TextImpl *>(m_text2->previousSibling());
     
-    KHTMLSelection &selection = m_document->view()->part()->getKHTMLSelection();
-    Range range(selection.startNode(), selection.startOffset(), selection.endNode(), selection.endOffset());
-    range.deleteContents();
-    selection.setSelection(selection.startNode(), selection.startOffset());
-    m_document->clearSelection();
+    return EditResultOK;
 }
 
-void EditCommand::removeNode(DOM::NodeImpl *node) const
+int ModifyTextNodeStep::joinTextNodes()
 {
-    if (!node)
-        return;
+    assert(m_text1);
+    assert(m_text2);
+    assert(state() == joinState());
     
+    if (m_text1->nextSibling() != m_text2)
+        return EditResultFailed;
+
     int exceptionCode;
-    node->remove(exceptionCode);
+    m_text2->insertData(0, m_text1->data(), exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    m_text2->parent()->removeChild(m_text2, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    m_offset = m_text1->length();
+    m_text1->deref();
+    m_text1 = 0;
+
+    return EditResultOK;
 }
 
 //------------------------------------------------------------------------------------------
-// InputTextCommand
+// SplitTextNodeStep
+
+SplitTextNodeStep::SplitTextNodeStep(DocumentImpl *document, TextImpl *text, long offset)
+    : ModifyTextNodeStep(document, text, offset)
+{
+}
+
+SplitTextNodeStep::~SplitTextNodeStep()
+{
+}
+
+int SplitTextNodeStep::apply()
+{
+    int result = splitTextNode();
+    if (result != EditResultOK)
+        return result;
+    else
+        return EditStep::apply(); // skips unimplemented ModifyTextNodeStep::apply()
+}
+
+int SplitTextNodeStep::unapply()
+{
+    int result = joinTextNodes();
+    if (result != EditResultOK)
+        return result;
+    else
+        return EditStep::unapply(); // skips unimplemented ModifyTextNodeStep::unapply()
+}
+
+//------------------------------------------------------------------------------------------
+// SplitTextNodeStep
+
+JoinTextNodesStep::JoinTextNodesStep(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
+    : ModifyTextNodeStep(document, text1, text2)
+{
+}
+
+JoinTextNodesStep::~JoinTextNodesStep()
+{
+}
+
+int JoinTextNodesStep::apply()
+{
+    int result = joinTextNodes();
+    if (result != EditResultOK)
+        return result;
+    else
+        return EditStep::apply(); // skips unimplemented ModifyTextNodeStep::apply()
+}
+
+int JoinTextNodesStep::unapply()
+{
+    int result = splitTextNode();
+    if (result != EditResultOK)
+        return result;
+    else
+        return EditStep::unapply(); // skips unimplemented ModifyTextNodeStep::unapply()
+}
 
-EditCommandID InputTextCommand::commandID() const { return InputTextCommandID; }
+//------------------------------------------------------------------------------------------
+// InsertTextStep
+
+InsertTextStep::InsertTextStep(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
+    : EditStep(document), m_node(node), m_offset(offset)
+{
+    assert(m_node);
+    assert(m_offset >= 0);
+    assert(text.length() > 0);
+    
+    m_node->ref();
+    m_text = text.copy(); // make a copy to ensure that the string never changes
+}
+
+InsertTextStep::~InsertTextStep()
+{
+    if (m_node)
+        m_node->deref();
+}
+
+int InsertTextStep::apply()
+{
+    assert(m_node);
+    assert(!m_text.isEmpty());
+    assert(state() == NOT_APPLIED);
+
+    int exceptionCode;
+    m_node->insertData(m_offset, m_text, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    return EditStep::apply();
+}
+
+int InsertTextStep::unapply()
+{
+    assert(m_node);
+    assert(!m_text.isEmpty());
+    assert(state() == APPLIED);
+
+    int exceptionCode;
+    m_node->deleteData(m_offset, m_text.length(), exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    return EditStep::unapply();
+}
+
+//------------------------------------------------------------------------------------------
+// DeleteTextStep
+
+DeleteTextStep::DeleteTextStep(DocumentImpl *document, TextImpl *node, long offset, long count)
+    : EditStep(document), m_node(node), m_offset(offset), m_count(count)
+{
+    assert(m_node);
+    assert(m_offset >= 0);
+    assert(m_count >= 0);
+    
+    m_node->ref();
+}
+
+DeleteTextStep::~DeleteTextStep()
+{
+    if (m_node)
+        m_node->deref();
+}
+
+int DeleteTextStep::apply()
+{
+    assert(m_node);
+    assert(state() == NOT_APPLIED);
+
+    int exceptionCode;
+    m_text = m_node->substringData(m_offset, m_count, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+    
+    m_node->deleteData(m_offset, m_count, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    return EditStep::apply();
+}
+
+int DeleteTextStep::unapply()
+{
+    assert(m_node);
+    assert(!m_text.isEmpty());
+    assert(state() == APPLIED);
+
+    int exceptionCode;
+    m_node->insertData(m_offset, m_text, exceptionCode);
+    if (exceptionCode)
+        return exceptionCode;
+
+    return EditStep::unapply();
+}
+
+//------------------------------------------------------------------------------------------
+// SetSelectionStep
+
+SetSelectionStep::SetSelectionStep(DocumentImpl *document, const KHTMLSelection &selection)
+    : EditStep(document)
+{
+    setEndingSelection(selection);
+}
+
+SetSelectionStep::SetSelectionStep(DocumentImpl *document, DOM::NodeImpl *node, long offset)
+    : EditStep(document)
+{
+    KHTMLSelection selection(node, offset);
+    setEndingSelection(selection);
+}
+
+SetSelectionStep::SetSelectionStep(DOM::DocumentImpl *document, const DOM::DOMPosition &pos)
+    : EditStep(document)
+{
+    KHTMLSelection selection(pos);
+    setEndingSelection(selection);
+}
+
+int SetSelectionStep::apply()
+{
+    assert(state() == NOT_APPLIED);
+    return EditStep::apply();
+}
+
+int SetSelectionStep::unapply()
+{
+    assert(state() == APPLIED);
+    return EditStep::unapply();
+}
+
+//------------------------------------------------------------------------------------------
+// DeleteSelectionStep
+
+DeleteSelectionStep::DeleteSelectionStep(DOM::DocumentImpl *document)
+    : CompositeEditStep(document)
+{
+}
+
+DeleteSelectionStep::DeleteSelectionStep(DOM::DocumentImpl *document, const KHTMLSelection &selection)
+    : CompositeEditStep(document)
+{
+    setStartingSelection(selection);
+}
+
+DeleteSelectionStep::~DeleteSelectionStep()
+{
+}
+	
+int DeleteSelectionStep::apply()
+{
+    assert(state() == NOT_APPLIED);
+
+    if (startingSelection().isEmpty())
+        return EditStep::apply();
+
+    KHTMLSelection selection = startingSelection();
+
+    //
+    // Figure out where to place the caret after doing the delete:
+    //
+    // 1. If the start node is not completely selected, use the start 
+    //    node and start offset for the new position; else
+    // 2. If the start and end nodes are completely selected:
+    //       a. If there is an editable node following the end node, 
+    //          place the caret in the min caret offset of that node; else
+    //       b. If there is an editable node before the start node, 
+    //          place the caret in the max caret offset of that node; else
+    //       c. There is no more editable content in the document.
+    //          EDIT FIXME: We do not handle this case now
+    // 3. If the start node is completely selected and the end node is 
+    //    different than the start node and it is not completely selected,
+    //    use the end node and the end node min caret for the new position; else
+    //
+
+    DOMPosition deleteStart = DOMPosition(selection.startNode(), selection.startOffset());
+    DOMPosition deleteEnd = DOMPosition(selection.endNode(), selection.endOffset());
+
+    bool startIsCompletelySelected = 
+        deleteStart.offset() == deleteStart.node()->caretMinOffset() &&
+        ((deleteStart.node() != deleteEnd.node()) ||
+         (deleteEnd.offset() == deleteEnd.node()->caretMaxOffset()));
+
+    bool endIsCompletelySelected = 
+        deleteEnd.offset() == deleteEnd.node()->caretMaxOffset() &&
+        ((deleteStart.node() != deleteEnd.node()) ||
+         (deleteStart.offset() == deleteStart.node()->caretMinOffset()));
+
+    DOMPosition endingPosition;
+
+    if (!startIsCompletelySelected) {
+        // Case 1
+        endingPosition = DOMPosition(deleteStart.node(), deleteStart.offset());
+    }
+    else if (endIsCompletelySelected) {
+        DOMPosition pos = selection.nextCharacterPosition(deleteEnd);
+        if (pos != deleteEnd) {
+            // Case 2a
+            endingPosition = DOMPosition(pos.node(), pos.node()->caretMinOffset());
+        }
+        else {
+            pos = selection.previousCharacterPosition(deleteStart);
+            if (pos != deleteStart) {
+                // Case 2b
+                endingPosition = DOMPosition(pos.node(), pos.node()->caretMaxOffset());
+            }
+            else {
+                // Case 2c
+                // EDIT FIXME
+                endingPosition = DOMPosition();
+            }
+        }
+    }
+    else {
+        // Case 3
+        endingPosition = DOMPosition(deleteEnd.node(), deleteEnd.node()->caretMinOffset());
+    }
+
+    //
+    // Do the delete
+    //
+    EditStep *step;
+    NodeImpl *n = deleteStart.node()->nextLeafNode();
+
+    // work on start node
+    if (startIsCompletelySelected) {
+        step = new RemoveNodeStep(document(), deleteStart.node());
+        APPLY_STEP(step);
+    }
+    else if (deleteStart.node()->isTextNode()) {
+        TextImpl *text = static_cast<TextImpl *>(deleteStart.node());
+        int endOffset = text == deleteEnd.node() ? deleteEnd.offset() : text->length();
+        if (endOffset > deleteStart.offset()) {
+            step = new DeleteTextStep(document(), text, deleteStart.offset(), endOffset - deleteStart.offset());
+            APPLY_STEP(step);
+        }
+    }
+    else {
+        // never should reach this code
+        assert(0);
+    }
+
+    if (deleteStart.node() != deleteEnd.node()) {
+        // work on intermediate nodes
+        while (n != deleteEnd.node()) {
+            NodeImpl *d = n;
+            n = n->nextLeafNode();
+            step = new RemoveNodeStep(document(), d);
+            APPLY_STEP(step);
+        }
+        
+        // work on end node
+        assert(n == deleteEnd.node());
+        if (endIsCompletelySelected) {
+            step = new RemoveNodeStep(document(), deleteEnd.node());
+            APPLY_STEP(step);
+        }
+        else if (deleteEnd.node()->isTextNode()) {
+            if (deleteEnd.offset() > 0) {
+                TextImpl *text = static_cast<TextImpl *>(deleteEnd.node());
+                step = new DeleteTextStep(document(), text, 0, deleteEnd.offset());
+                APPLY_STEP(step);
+            }
+        }
+        else {
+            // never should reach this code
+            assert(0);
+        }
+    }
+
+    //
+    // set the ending selection
+    //
+    selection.moveTo(endingPosition);
+    selection.moveToRenderedContent();
+    setEndingSelection(selection);
+
+    return CompositeEditStep::apply();
+}
+
+//------------------------------------------------------------------------------------------
+
+#pragma mark EditCommands
+
+//------------------------------------------------------------------------------------------
+// EditCommand
+
+static int cookieCounter = 0;
+
+EditCommand::EditCommand(DocumentImpl *document) : m_document(document)
+{
+    assert(m_document);
+    m_document->ref();
+    m_cookie = cookieCounter++;
+    m_steps.setAutoDelete(true);
+}
+
+EditCommand::~EditCommand()
+{
+    assert(m_document);
+    m_document->deref();
+}
+
+const KHTMLSelection &EditCommand::selection() const
+{
+    assert(m_document);
+    assert(m_document->part());
+    return m_document->part()->selection();
+}
+
+int EditCommand::unapply()
+{
+    assert(m_steps.count() > 0);
+    
+    QPtrListIterator<EditStep> it(m_steps);
+    for (it.toLast(); it.current(); --it) {
+        int result = it.current()->unapply();
+        if (result != EditResultOK)
+            return result;
+    }
+
+    return EditResultOK;
+}
+
+int EditCommand::reapply()
+{
+    assert(m_steps.count() > 0);
+    
+    QPtrListIterator<EditStep> it(m_steps);
+    for (; it.current(); ++it) {
+        int result = it.current()->reapply();
+        if (result != EditResultOK)
+            return result;
+    }
+
+    return EditResultOK;
+}
+
+//------------------------------------------------------------------------------------------
+// InputTextCommand
 
 InputTextCommand::InputTextCommand(DocumentImpl *document, const DOMString &text) 
     : EditCommand(document)
@@ -120,103 +849,107 @@ bool InputTextCommand::isSpace() const
     return m_text.length() == 1 && (m_text[0] == ' ');
 }
 
-bool InputTextCommand::apply()
+int InputTextCommand::apply()
 {
-    KHTMLView *view = document()->view();
-    if (!view)
-        return false;
+    KHTMLPart *part = document()->part();
+    assert(part);
 
-    KHTMLPart *part = view->part();
-    if (!part)
-        return false;
-
-    KHTMLSelection &selection = part->getKHTMLSelection();
+    KHTMLSelection selection = part->selection();
     if (!selection.startNode()->isTextNode())
-        return false;
+        return EditResultFailed;
+
+    EditStep *step;
 
     // Delete the current selection
     if (selection.state() == KHTMLSelection::RANGE) {
-        deleteSelection();
-        // EDIT FIXME: adjust selection position
+        step = new DeleteSelectionStep(document());
+        APPLY_STEP(step);
     }
     
     TextImpl *textNode = static_cast<TextImpl *>(selection.startNode());
-    int exceptionCode;
     
     if (isLineBreak()) {
+        int exceptionCode;
         ElementImpl *breakNode = document()->createHTMLElement("BR", exceptionCode);
-        
+
         bool atStart = selection.startOffset() == textNode->renderer()->caretMinOffset();
         bool atEnd = selection.startOffset() == textNode->renderer()->caretMaxOffset();
         if (atStart) {
-            textNode->parentNode()->insertBefore(breakNode, textNode, exceptionCode);
             // Set the cursor at the beginning of text node now following the new BR.
-            selection.setSelection(textNode, 0);
+            step = new InsertNodeBeforeStep(document(), breakNode, textNode);
+            APPLY_STEP(step);
+
+            step = new SetSelectionStep(document(), textNode, 0);
+            APPLY_STEP(step);
         }
         else if (atEnd) {
-            if (textNode->parentNode()->lastChild() == textNode)
-                textNode->parentNode()->appendChild(breakNode, exceptionCode);
-            else
-                textNode->parentNode()->insertBefore(breakNode, textNode->nextSibling(), exceptionCode);
+            if (textNode->parentNode()->lastChild() == textNode) {
+                step = new AppendNodeStep(document(), textNode->parentNode(), breakNode);
+                APPLY_STEP(step);
+            }
+            else {
+                step = new InsertNodeBeforeStep(document(), breakNode, textNode->nextSibling());
+                APPLY_STEP(step);
+            }
             // Set the cursor at the beginning of the the BR.
-            selection.setSelection(selection.nextCharacterPosition());
+            step = new SetSelectionStep(document(), selection.nextCharacterPosition());
+            APPLY_STEP(step);
         }
         else {
             TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.startOffset(), exceptionCode));
-            textNode->deleteData(0, selection.startOffset(), exceptionCode);
-            textNode->parentNode()->insertBefore(textBeforeNode, textNode, exceptionCode);
-            textNode->parentNode()->insertBefore(breakNode, textNode, exceptionCode);
+            step = new DeleteTextStep(document(), textNode, 0, selection.startOffset());
+            APPLY_STEP(step);
+
+            step = new InsertNodeBeforeStep(document(), textBeforeNode, textNode);
+            APPLY_STEP(step);
+
+            step = new InsertNodeBeforeStep(document(), breakNode, textNode);
+            APPLY_STEP(step);
+
             textBeforeNode->deref();
             // Set the cursor at the beginning of the node after the BR.
-            selection.setSelection(textNode, 0);
+            step = new SetSelectionStep(document(), textNode, 0);
+            APPLY_STEP(step);
         }
         
         breakNode->deref();
     }
     else {
-        textNode->insertData(selection.startOffset(), text(), exceptionCode);
-        selection.setSelection(selection.startNode(), selection.startOffset() + text().length());
-    }
+        step = new InsertTextStep(document(), textNode, selection.startOffset(), text());
+        APPLY_STEP(step);
 
-    return true;
-}
+        step = new SetSelectionStep(document(), selection.startNode(), selection.startOffset() + text().length());
+        APPLY_STEP(step);
+    }
 
-bool InputTextCommand::canUndo() const
-{
-    return true;
+    return EditResultOK;
 }
 
 //------------------------------------------------------------------------------------------
 // DeleteTextCommand
 
-EditCommandID DeleteTextCommand::commandID() const { return DeleteTextCommandID; }
-
 DeleteTextCommand::DeleteTextCommand(DocumentImpl *document) 
     : EditCommand(document)
 {
 }
 
-bool DeleteTextCommand::apply()
+int DeleteTextCommand::apply()
 {
-    KHTMLView *view = document()->view();
-    if (!view)
-        return false;
-
-    KHTMLPart *part = view->part();
-    if (!part)
-        return false;
+    KHTMLPart *part = document()->part();
+    assert(part);
 
-    KHTMLSelection &selection = part->getKHTMLSelection();
+    KHTMLSelection selection = part->selection();
+    EditStep *step;
 
     // Delete the current selection
     if (selection.state() == KHTMLSelection::RANGE) {
-        deleteSelection();
-        // EDIT FIXME: adjust selection position
-        return true;
+        step = new DeleteSelectionStep(document());
+        APPLY_STEP(step);
+        return EditResultOK;
     }
 
     if (!selection.startNode())
-        return false;
+        return EditResultFailed;
 
     NodeImpl *caretNode = selection.startNode();
 
@@ -228,7 +961,7 @@ bool DeleteTextCommand::apply()
         if (offset >= 0) {
             TextImpl *textNode = static_cast<TextImpl *>(caretNode);
             textNode->deleteData(offset, 1, exceptionCode);
-            selection.setSelection(textNode, offset);
+            selection.moveTo(textNode, offset);
             return true;
         }
         
@@ -246,16 +979,10 @@ bool DeleteTextCommand::apply()
             TextImpl *textNode = static_cast<TextImpl *>(previousLeafNode);
             offset = previousLeafNode->caretMaxOffset() - 1;
             textNode->deleteData(offset, 1, exceptionCode);
-            selection.setSelection(textNode, offset);
+            selection.moveTo(textNode, offset);
             return true;
         }
     }
 
     return false;
 }
-
-bool DeleteTextCommand::canUndo() const
-{
-    return true;
-}
-
diff --git a/WebCore/khtml/editing/htmlediting.h b/WebCore/khtml/editing/htmlediting.h
index 95fd2bf..4305b31 100644
--- a/WebCore/khtml/editing/htmlediting.h
+++ b/WebCore/khtml/editing/htmlediting.h
@@ -27,17 +27,238 @@
 #define __htmlediting_h__
 
 #include <khtml_selection.h>
-#include <dom_docimpl.h>
+#include <dom_doc.h>
+#include <dom_position.h>
 #include <dom_string.h>
-#include <dom_node.h>
-#include <dom_nodeimpl.h>
-#include <dom2_range.h>
+
+#include <qptrlist.h>
 
 class KHTMLSelection;
 
+namespace DOM {
+    class DocumentFragment;
+    class DocumentImpl;
+    class DOMPosition;
+    class DOMString;
+    class NodeImpl;
+    class TextImpl;
+};
+
 namespace khtml {
 
-enum EditCommandID { InputTextCommandID, DeleteTextCommandID, };
+//------------------------------------------------------------------------------------------
+// Edit result codes
+
+enum {
+    EditResultOK                 = 0,
+    EditResultFailed             = -1,
+    EditResultNoActionTaken      = -2,
+};
+
+//------------------------------------------------------------------------------------------
+// EditStep classes
+
+class EditStep
+{
+public:
+	EditStep(DOM::DocumentImpl *);
+	virtual ~EditStep();
+	
+    enum EditStepState { NOT_APPLIED, APPLIED };
+    
+	virtual int apply();	
+	virtual int unapply();
+	virtual int reapply();
+    
+    DOM::DocumentImpl * const document() const { return m_document; }
+    EditStepState state() const { return m_state; }
+
+    const KHTMLSelection &startingSelection() const { return m_startingSelection; }
+    const KHTMLSelection &endingSelection() const { return m_endingSelection; }
+
+protected:
+    void setStartingSelection(const KHTMLSelection &s) { m_startingSelection = s; }
+    void setEndingSelection(const KHTMLSelection &s) { m_endingSelection = s; }
+
+private:
+    DOM::DocumentImpl *m_document;
+    EditStepState m_state;
+    KHTMLSelection m_startingSelection;
+    KHTMLSelection m_endingSelection;
+};
+
+class CompositeEditStep : public EditStep
+{
+public:
+	CompositeEditStep(DOM::DocumentImpl *);
+	virtual ~CompositeEditStep();
+	
+	virtual int unapply();
+	virtual int reapply();
+    
+protected:
+    QPtrList<EditStep> m_steps;
+};
+
+class InsertNodeBeforeStep : public EditStep
+{
+public:
+    InsertNodeBeforeStep(DOM::DocumentImpl *, DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
+	virtual ~InsertNodeBeforeStep();
+
+	virtual int apply();
+	virtual int unapply();
+
+private:
+    DOM::NodeImpl *m_insertChild;
+    DOM::NodeImpl *m_refChild;    
+};
+
+class AppendNodeStep : public EditStep
+{
+public:
+    AppendNodeStep(DOM::DocumentImpl *, DOM::NodeImpl *parent, DOM::NodeImpl *appendChild);
+	virtual ~AppendNodeStep();
+
+	virtual int apply();
+	virtual int unapply();
+
+private:
+    DOM::NodeImpl *m_parent;    
+    DOM::NodeImpl *m_appendChild;
+};
+
+class RemoveNodeStep : public EditStep
+{
+public:
+	RemoveNodeStep(DOM::DocumentImpl *, DOM::NodeImpl *);
+	virtual ~RemoveNodeStep();
+	
+	virtual int apply();
+	virtual int unapply();
+
+private:
+    DOM::NodeImpl *m_parent;    
+    DOM::NodeImpl *m_removeChild;
+    DOM::NodeImpl *m_refChild;    
+};
+
+class ModifyTextNodeStep : public EditStep
+{
+public:
+    // used by SplitTextNodeStep derived class
+	ModifyTextNodeStep(DOM::DocumentImpl *, DOM::TextImpl *, long); 
+    // used by JoinTextNodesStep derived class
+    ModifyTextNodeStep(DOM::DocumentImpl *, DOM::TextImpl *, DOM::TextImpl *);
+	virtual ~ModifyTextNodeStep();
+	
+protected:
+    int splitTextNode();
+    int joinTextNodes();
+
+    virtual EditStepState joinState() = 0;
+    virtual EditStepState splitState() = 0;
+
+    DOM::TextImpl *m_text1;
+    DOM::TextImpl *m_text2;
+    long m_offset;
+};
+
+class SplitTextNodeStep : public ModifyTextNodeStep
+{
+public:
+	SplitTextNodeStep(DOM::DocumentImpl *, DOM::TextImpl *, long);
+	virtual ~SplitTextNodeStep();
+	
+	virtual int apply();
+	virtual int unapply();
+
+    virtual EditStepState joinState() { return APPLIED; }
+    virtual EditStepState splitState() { return NOT_APPLIED; }
+};
+
+class JoinTextNodesStep : public ModifyTextNodeStep
+{
+public:
+	JoinTextNodesStep(DOM::DocumentImpl *, DOM::TextImpl *, DOM::TextImpl *);
+	virtual ~JoinTextNodesStep();
+	
+	virtual int apply();
+	virtual int unapply();
+
+    virtual EditStepState joinState() { return NOT_APPLIED; }
+    virtual EditStepState splitState() { return APPLIED; }
+};
+
+class InsertTextStep : public EditStep
+{
+public:
+	InsertTextStep(DOM::DocumentImpl *document, DOM::TextImpl *, long, const DOM::DOMString &);
+	virtual ~InsertTextStep();
+	
+	virtual int apply();
+	virtual int unapply();
+
+private:
+    DOM::TextImpl *m_node;
+    long m_offset;
+    DOM::DOMString m_text;
+};
+
+class DeleteTextStep : public EditStep
+{
+public:
+	DeleteTextStep(DOM::DocumentImpl *document, DOM::TextImpl *, long offset, long count);
+	virtual ~DeleteTextStep();
+	
+	virtual int apply();
+	virtual int unapply();
+
+private:
+    DOM::TextImpl *m_node;
+    long m_offset;
+    long m_count;
+    DOM::DOMString m_text;
+};
+
+class SetSelectionStep : public EditStep
+{
+public:
+	SetSelectionStep(DOM::DocumentImpl *document, const KHTMLSelection &selection);
+	SetSelectionStep(DOM::DocumentImpl *document, DOM::NodeImpl *, long);
+	SetSelectionStep(DOM::DocumentImpl *document, const DOM::DOMPosition &);
+	virtual ~SetSelectionStep() {};
+	
+	virtual int apply();
+	virtual int unapply();
+};
+
+class DeleteSelectionStep : public CompositeEditStep
+{
+public:
+	DeleteSelectionStep(DOM::DocumentImpl *document);
+	DeleteSelectionStep(DOM::DocumentImpl *document, const KHTMLSelection &);
+	virtual ~DeleteSelectionStep();
+	
+	virtual int apply();
+};
+
+#if 0
+
+class ClearSelectionStep : public EditStep
+{
+public:
+	ClearSelectionStep(DOM::DocumentImpl *document) : EditStep(document) {};
+	virtual ~ClearSelectionStep() {};
+	
+	virtual int apply() { return EditResultOK; }	
+	virtual int unapply() { return EditResultOK; }
+};
+
+#endif
+
+//------------------------------------------------------------------------------------------
+// EditCommand
 
 class EditCommand
 {
@@ -45,19 +266,20 @@ public:
     EditCommand(DOM::DocumentImpl *document);
     virtual ~EditCommand();
 
-    virtual EditCommandID commandID() const = 0;
-
     DOM::DocumentImpl *document() const { return m_document; }
+    const KHTMLSelection &selection() const;
+
+    virtual int apply() = 0;
+    int unapply();
+    int reapply();
+
+    int cookie() const { return m_cookie; }
 
-    virtual bool apply() = 0;
-    virtual bool canUndo() const = 0;
-    
 protected:
-    void deleteSelection();
-    void pruneEmptyNodes() const;
-    void removeNode(DOM::NodeImpl *) const;
+    QPtrList<EditStep> m_steps;
     
 private:
+    int m_cookie;
     DOM::DocumentImpl *m_document;
 };
 
@@ -68,10 +290,7 @@ public:
     InputTextCommand(DOM::DocumentImpl *document, const DOM::DOMString &text);
     virtual ~InputTextCommand() {};
     
-    virtual EditCommandID commandID() const;
-    
-    virtual bool apply();
-    virtual bool canUndo() const;
+    virtual int apply();
 
     DOM::DOMString text() const { return m_text; }
     bool isLineBreak() const;
@@ -87,10 +306,7 @@ public:
     DeleteTextCommand(DOM::DocumentImpl *document);
     virtual ~DeleteTextCommand() {};
     
-    virtual EditCommandID commandID() const;
-    
-    virtual bool apply();
-    virtual bool canUndo() const;
+    virtual int apply();
 };
 
 }; // end namespace khtml
diff --git a/WebCore/khtml/editing/selection.cpp b/WebCore/khtml/editing/selection.cpp
index c631e6d..91e3f1c 100644
--- a/WebCore/khtml/editing/selection.cpp
+++ b/WebCore/khtml/editing/selection.cpp
@@ -61,8 +61,6 @@ using khtml::InlineTextBox;
 using khtml::RenderObject;
 using khtml::RenderText;
 
-enum { CARET_BLINK_FREQUENCY = 500 };
-
 #if APPLE_CHANGES
 static void findWordBoundary(QChar *chars, int len, int position, int *start, int *end);
 static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset);
@@ -71,35 +69,94 @@ static bool startAndEndLineNodesIncludingNode(DOM::NodeImpl *node, int offset, K
 #endif
 
 
-KHTMLSelection::KHTMLSelection() 
-	: QObject(),
-	  m_part(0),
-	  m_baseNode(0), m_baseOffset(0), m_extentNode(0), m_extentOffset(0),
-	  m_startNode(0), m_startOffset(0), m_endNode(0), m_endOffset(0),
-	  m_state(NONE), m_caretBlinkTimer(0),
-      m_baseIsStart(true), m_caretBlinks(true), m_caretPaint(false), 
-      m_visible(false), m_startEndValid(false)
+KHTMLSelection::KHTMLSelection()
+{
+    init();
+}
+
+KHTMLSelection::KHTMLSelection(NodeImpl *node, long offset)
 {
+    init();
+
+	setBaseNode(node);
+	setExtentNode(node);
+	setBaseOffset(offset);
+	setExtentOffset(offset);
+
+    validate();
+}
+
+KHTMLSelection::KHTMLSelection(const DOM::DOMPosition &pos)
+{
+    init();
+
+	setBaseNode(pos.node());
+	setExtentNode(pos.node());
+	setBaseOffset(pos.offset());
+	setExtentOffset(pos.offset());
+
+    validate();
+}
+
+KHTMLSelection::KHTMLSelection(NodeImpl *baseNode, long baseOffset, NodeImpl *endNode, long endOffset)
+{
+    init();
+
+	setBaseNode(baseNode);
+	setExtentNode(endNode);
+	setBaseOffset(baseOffset);
+	setExtentOffset(endOffset);
+
+    validate();
 }
 
 KHTMLSelection::KHTMLSelection(const KHTMLSelection &o)
-	: QObject(),
-	  m_part(o.m_part),
-	  m_baseNode(0), m_baseOffset(0), m_extentNode(0), m_extentOffset(0),
-	  m_startNode(0), m_startOffset(0), m_endNode(0), m_endOffset(0)
 {
-    setBase(o.baseNode(), o.baseOffset());
-    setExtent(o.extentNode(), o.extentOffset());
-    setStart(o.startNode(), o.startOffset());
-    setEnd(o.endNode(), o.endOffset());
-
-	m_state = o.m_state;
-	m_caretBlinkTimer = o.m_caretBlinkTimer;
-	m_baseIsStart = o.m_baseIsStart;
-	m_caretBlinks = o.m_caretBlinks;
-	m_caretPaint = true;
-	m_visible = o.m_visible;
-    m_startEndValid = true;
+    init();
+    
+	setBaseNode(o.baseNode());
+	setExtentNode(o.extentNode());
+	setBaseOffset(o.baseOffset());
+	setExtentOffset(o.extentOffset());
+
+	setStartNode(o.startNode());
+	setEndNode(o.endNode());
+	setStartOffset(o.startOffset());
+	setEndOffset(o.endOffset());
+
+    m_state = o.m_state;
+
+    m_baseIsStart = o.m_baseIsStart;
+    m_needsCaretLayout = o.m_needsCaretLayout;
+
+    // Only copy the coordinates over if the other object
+    // has had a layout, otherwise keep the current
+    // coordinates. This prevents drawing artifacts from
+    // remaining when the caret is painted and then moves,
+    // and the old rectangle needs to be repainted.
+    if (!m_needsCaretLayout) {
+        m_caretX = o.m_caretX;
+        m_caretY = o.m_caretY;
+        m_caretSize = o.m_caretSize;
+    }
+}
+
+void KHTMLSelection::init()
+{
+    m_baseNode = 0;
+    m_baseOffset = 0;
+    m_extentNode = 0; 
+    m_extentOffset = 0;
+    m_startNode = 0;
+    m_startOffset = 0;
+    m_endNode = 0;
+    m_endOffset = 0;
+    m_state = NONE; 
+    m_caretX = 0;
+    m_caretY = 0;
+    m_caretSize = 0;
+    m_baseIsStart = true;
+    m_needsCaretLayout = true;
 }
 
 KHTMLSelection::~KHTMLSelection()
@@ -108,77 +165,74 @@ KHTMLSelection::~KHTMLSelection()
         m_baseNode->deref();
     if (m_extentNode)
         m_extentNode->deref();
+    if (m_startNode)
+        m_startNode->deref();
+    if (m_endNode)
+        m_endNode->deref();
 }
 
 KHTMLSelection &KHTMLSelection::operator=(const KHTMLSelection &o)
 {
-    m_part = o.m_part;
+	setBaseNode(o.baseNode());
+	setExtentNode(o.extentNode());
+	setBaseOffset(o.baseOffset());
+	setExtentOffset(o.extentOffset());
+
+	setStartNode(o.startNode());
+	setEndNode(o.endNode());
+	setStartOffset(o.startOffset());
+	setEndOffset(o.endOffset());
+
+    m_state = o.m_state;
+
+    m_baseIsStart = o.m_baseIsStart;
+    m_needsCaretLayout = o.m_needsCaretLayout;
+    
+    // Only copy the coordinates over if the other object
+    // has had a layout, otherwise keep the current
+    // coordinates. This prevents drawing artifacts from
+    // remaining when the caret is painted and then moves,
+    // and the old rectangle needs to be repainted.
+    if (!m_needsCaretLayout) {
+        m_caretX = o.m_caretX;
+        m_caretY = o.m_caretY;
+        m_caretSize = o.m_caretSize;
+    }
     
-    setBase(o.baseNode(), o.baseOffset());
-    setExtent(o.extentNode(), o.extentOffset());
-    setStart(o.startNode(), o.startOffset());
-    setEnd(o.endNode(), o.endOffset());
-
-	m_state = o.m_state;
-	m_caretBlinkTimer = o.m_caretBlinkTimer;
-	m_baseIsStart = o.m_baseIsStart;
-	m_caretBlinks = o.m_caretBlinks;
-	m_caretPaint = true;
-	m_visible = o.m_visible;
-    m_startEndValid = true;
     return *this;
 }
 
-void KHTMLSelection::setSelection(DOM::NodeImpl *node, long offset)
+void KHTMLSelection::moveTo(DOM::NodeImpl *node, long offset)
 {
-	setBaseNode(node);
-	setExtentNode(node);
-	setBaseOffset(offset);
-	setExtentOffset(offset);
-	update();
-#if EDIT_DEBUG
-    debugPosition();
-#endif
+    moveTo(node, offset, node, offset);
 }
 
-void KHTMLSelection::setSelection(const DOM::Range &r)
+void KHTMLSelection::moveTo(const DOM::Range &r)
 {
-	setSelection(r.startContainer().handle(), r.startOffset(), 
+	moveTo(r.startContainer().handle(), r.startOffset(), 
 		r.endContainer().handle(), r.endOffset());
 }
 
-void KHTMLSelection::setSelection(const DOM::DOMPosition &pos)
+void KHTMLSelection::moveTo(const DOM::DOMPosition &pos)
 {
-	setSelection(pos.node(), pos.offset());
+	moveTo(pos.node(), pos.offset());
 }
 
-void KHTMLSelection::setSelection(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset)
+void KHTMLSelection::moveTo(const KHTMLSelection &o)
+{
+	moveTo(o.baseNode(), o.baseOffset(), o.extentNode(), o.extentOffset());
+}
+
+void KHTMLSelection::moveTo(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset)
 {
 	setBaseNode(baseNode);
 	setExtentNode(extentNode);
 	setBaseOffset(baseOffset);
 	setExtentOffset(extentOffset);
-	update();
-#if EDIT_DEBUG
-    debugPosition();
-#endif
-}
-
-void KHTMLSelection::setBase(DOM::NodeImpl *node, long offset)
-{
-	setBaseNode(node);
-	setBaseOffset(offset);
-	update();
+	validate();
 }
 
-void KHTMLSelection::setExtent(DOM::NodeImpl *node, long offset)
-{
-	setExtentNode(node);
-	setExtentOffset(offset);
-	update();
-}
-
-bool KHTMLSelection::alterSelection(EAlter alter, EDirection dir, ETextElement elem)
+bool KHTMLSelection::modify(EAlter alter, EDirection dir, ETextElement elem)
 {
     DOMPosition pos;
     
@@ -211,187 +265,137 @@ bool KHTMLSelection::alterSelection(EAlter alter, EDirection dir, ETextElement e
         return false;
     
     if (alter == MOVE)
-        setSelection(pos.node(), pos.offset());
+        moveTo(pos.node(), pos.offset());
     else // alter == EXTEND
         setExtent(pos.node(), pos.offset());
     
     return true;
 }
 
-void KHTMLSelection::clearSelection()
+void KHTMLSelection::expandToElement(ETextElement select)
+{
+    validate(select);
+}
+
+void KHTMLSelection::clear()
 {
 	setBaseNode(0);
 	setExtentNode(0);
 	setBaseOffset(0);
 	setExtentOffset(0);
-	update();
-}
-
-NodeImpl *KHTMLSelection::startNode() const
-{ 
-    return m_startNode;
+	validate();
 }
 
-long KHTMLSelection::startOffset() const
-{ 
-    return m_startOffset;
+void KHTMLSelection::setBase(DOM::NodeImpl *node, long offset)
+{
+	setBaseNode(node);
+	setBaseOffset(offset);
+	validate();
 }
 
-NodeImpl *KHTMLSelection::endNode() const 
+void KHTMLSelection::setExtent(DOM::NodeImpl *node, long offset)
 {
-    return m_endNode;
+	setExtentNode(node);
+	setExtentOffset(offset);
+	validate();
 }
 
-long KHTMLSelection::endOffset() const 
-{ 
-    return m_endOffset;
+void KHTMLSelection::setNeedsLayout(bool flag)
+{
+    m_needsCaretLayout = flag;
 }
 
-void KHTMLSelection::setVisible(bool flag)
+bool KHTMLSelection::isEmpty() const
 {
-    m_visible = flag;
-    update();
+    return m_baseNode == 0 && m_extentNode == 0;
 }
 
-void KHTMLSelection::invalidate()
+Range KHTMLSelection::toRange() const
 {
-    update();
+    if (isEmpty())
+        return Range();
+
+    return Range(Node(startNode()), startOffset(), Node(endNode()), endOffset());
 }
 
-void KHTMLSelection::update()
+void KHTMLSelection::layoutCaret()
 {
-    // make sure we do not have a dangling start or end
-	if (!m_baseNode && !m_extentNode) {
-        setBaseOffset(0);
-        setExtentOffset(0);
-        m_baseIsStart = true;
+    if (isEmpty() || !startNode()->renderer()) {
+        m_caretX = m_caretY = m_caretSize = 0;
     }
-	else if (!m_baseNode) {
-		setBaseNode(m_extentNode);
-		setBaseOffset(m_extentOffset);
-        m_baseIsStart = true;
-	}
-	else if (!m_extentNode) {
-		setExtentNode(m_baseNode);
-		setExtentOffset(m_baseOffset);
-        m_baseIsStart = true;
-	}
     else {
-        // adjust m_baseIsStart as needed
-        if (m_baseNode == m_extentNode) {
-            if (m_baseOffset > m_extentOffset)
-                m_baseIsStart = false;
-            else 
-                m_baseIsStart = true;
-        }
-        else if (nodeIsBeforeNode(m_baseNode, m_extentNode))
-            m_baseIsStart = true;
-        else
-            m_baseIsStart = false;
+        int w;
+        startNode()->renderer()->caretPos(startOffset(), true, m_caretX, m_caretY, w, m_caretSize);
     }
 
-    // update start and end
-    m_startEndValid = false;
-    calculateStartAndEnd();
-    
-    // update the blink timer
-    if (m_caretBlinkTimer >= 0)
-        killTimer(m_caretBlinkTimer);
-    if (m_visible && m_state == CARET && m_caretBlinks)
-        m_caretBlinkTimer = startTimer(CARET_BLINK_FREQUENCY);
-    else
-        m_caretBlinkTimer = -1;
+    m_needsCaretLayout = false;
+}
 
-    // short-circuit if not visible
-    if (!m_visible) {
-        if (m_caretPaint) {
-            m_caretPaint = false;
-            repaint();
-        }
-        return;
-    }
+QRect KHTMLSelection::getRepaintRect()
+{
+    // EDIT FIXME: fudge a bit to make sure we don't leave behind artifacts
+    return QRect(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2);
+}
 
-    // short-circuit if not CARET state
-	if (m_state != CARET)
-		return;
+void KHTMLSelection::needsCaretRepaint()
+{
+    if (isEmpty())
+        return;
 
-    // calculate the new caret rendering position
-    int oldX = m_caretX;   
-    int oldY = m_caretY;   
-    int oldSize = m_caretSize;
-    
-    int newX = 0;
-    int newY = 0;
-    int newSize = 0;
-    
-    NodeImpl *node = startNode();
-    if (node && node->renderer()) {
-        int w;
-        node->renderer()->caretPos(startOffset(), true, newX, newY, w, newSize);
-    }
+    if (!startNode()->getDocument())
+        return;
 
-    // repaint the old position if necessary
-    // prevents two carets from ever being drawn
-    if (m_caretPaint && (oldX != newX || oldY != newY || oldSize != newSize)) {
-        repaint();
-    }
+    KHTMLView *v = startNode()->getDocument()->view();
+    if (!v)
+        return;
 
-    // update caret rendering position
-    m_caretX = newX;
-    m_caretY = newY;
-    m_caretSize = newSize;
-    
-    // paint the caret if it is visible
-    if (m_visible && m_caretSize != 0) {
-        m_caretPaint = true;
-        repaint();
+    if (m_needsCaretLayout) {
+        // repaint old position and calculate new position
+        v->updateContents(getRepaintRect(), false);
+        layoutCaret();
+        
+        // EDIT FIXME: This is an unfortunate hack.
+        // Basically, we can't trust this layout position since we 
+        // can't guarantee that the check to see if we are in unrendered 
+        // content will work at this point. We may have to wait for
+        // a layout and re-render of the document to happen. So, resetting this
+        // flag will cause another caret layout to happen the first time
+        // that we try to paint the caret after this call. That one will work since
+        // it happens after the document has accounted for any editing
+        // changes which may have been done.
+        // And, we need to leave this layout here so the caret moves right 
+        // away after clicking.
+        m_needsCaretLayout = true;
     }
+    v->updateContents(getRepaintRect(), false);
 }
 
-bool KHTMLSelection::isEmpty() const
+void KHTMLSelection::paintCaret(QPainter *p, const QRect &rect)
 {
-    return m_baseNode == 0 && m_extentNode == 0;
-}
+    if (isEmpty())
+        return;
 
-#ifdef APPLE_CHANGES
-void KHTMLSelection::paint(QPainter *p, const QRect &rect) const
-{
-    if (!m_caretPaint || m_state != CARET)
+    if (m_state != CARET)
         return;
 
-    QRect pos(m_caretX, m_caretY, 1, m_caretSize);
-    if (pos.intersects(rect)) {
+    if (m_needsCaretLayout) {
+        DOMPosition pos = DOMPosition(startNode(), startOffset());
+        if (!inRenderedContent(pos)) {
+            moveToRenderedContent();
+        }
+        layoutCaret();
+    }
+
+    QRect caretRect(m_caretX, m_caretY, 1, m_caretSize);
+    if (caretRect.intersects(rect)) {
         QPen pen = p->pen();
-        pen.setStyle(SolidLine);
+        pen.setStyle(Qt::SolidLine);
         pen.setColor(Qt::black);
         pen.setWidth(1);
         p->setPen(pen);
-        p->drawLine(pos.left(), pos.top(), pos.left(), pos.bottom());
+        p->drawLine(caretRect.left(), caretRect.top(), caretRect.left(), caretRect.bottom());
     }
 }
-#endif
-
-void KHTMLSelection::setPart(KHTMLPart *part)
-{
-    m_part = part;
-}
-
-void KHTMLSelection::timerEvent(QTimerEvent *e)
-{
-    if (e->timerId() == m_caretBlinkTimer && m_visible) {
-        m_caretPaint = !m_caretPaint;
-        repaint();
-    }
-}
-
-void KHTMLSelection::repaint(bool immediate) const
-{
-    KHTMLView *v = m_part->view();
-    if (!v)
-        return;
-    // EDIT FIXME: fudge a bit to make sure we don't leave behind artifacts
-    v->updateContents(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2, immediate);
-}
 
 void KHTMLSelection::setBaseNode(DOM::NodeImpl *node)
 {
@@ -431,12 +435,6 @@ void KHTMLSelection::setExtentOffset(long offset)
 	m_extentOffset = offset;
 }
 
-void KHTMLSelection::setStart(DOM::NodeImpl *node, long offset)
-{
-    setStartNode(node);
-    setStartOffset(offset);
-}
-
 void KHTMLSelection::setStartNode(DOM::NodeImpl *node)
 {
 	if (m_startNode == node)
@@ -456,12 +454,6 @@ void KHTMLSelection::setStartOffset(long offset)
 	m_startOffset = offset;
 }
 
-void KHTMLSelection::setEnd(DOM::NodeImpl *node, long offset)
-{
-    setEndNode(node);
-    setEndOffset(offset);
-}
-
 void KHTMLSelection::setEndNode(DOM::NodeImpl *node)
 {
 	if (m_endNode == node)
@@ -481,17 +473,39 @@ void KHTMLSelection::setEndOffset(long offset)
 	m_endOffset = offset;
 }
 
-void KHTMLSelection::expandSelection(ETextElement select)
-{
-    m_startEndValid = false;
-    calculateStartAndEnd(select);
-}
-
-void KHTMLSelection::calculateStartAndEnd(ETextElement select)
+void KHTMLSelection::validate(ETextElement expandTo)
 {
-    if (m_startEndValid)
-        return;
+    // make sure we do not have a dangling start or end
+	if (!m_baseNode && !m_extentNode) {
+        setBaseOffset(0);
+        setExtentOffset(0);
+        m_baseIsStart = true;
+    }
+	else if (!m_baseNode) {
+		setBaseNode(m_extentNode);
+		setBaseOffset(m_extentOffset);
+        m_baseIsStart = true;
+	}
+	else if (!m_extentNode) {
+		setExtentNode(m_baseNode);
+		setExtentOffset(m_baseOffset);
+        m_baseIsStart = true;
+	}
+    else {
+        // adjust m_baseIsStart as needed
+        if (m_baseNode == m_extentNode) {
+            if (m_baseOffset > m_extentOffset)
+                m_baseIsStart = false;
+            else 
+                m_baseIsStart = true;
+        }
+        else if (nodeIsBeforeNode(m_baseNode, m_extentNode))
+            m_baseIsStart = true;
+        else
+            m_baseIsStart = false;
+    }
 
+    // calculate the correct start and end positions
 #if !APPLE_CHANGES
     if (m_baseIsStart) {
         setStartNode(m_baseNode);
@@ -506,7 +520,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
         setEndOffset(m_baseOffset);
     }
 #else
-    if (select == CHARACTER) {
+    if (expandTo == CHARACTER) {
         if (m_baseIsStart) {
             setStartNode(m_baseNode);
             setStartOffset(m_baseOffset);
@@ -520,7 +534,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
             setEndOffset(m_baseOffset);
         }
     }
-    else if (select == WORD) {
+    else if (expandTo == WORD) {
         int baseStartOffset = m_baseOffset;
         int baseEndOffset = m_baseOffset;
         int extentStartOffset = m_extentOffset;
@@ -550,7 +564,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
             setEndOffset(baseEndOffset);
         }
     }
-    else {  // select == LINE
+    else {  // expandTo == LINE
         KHTMLSelection baseSelection = *this;
         KHTMLSelection extentSelection = *this;
         if (m_baseNode && (m_baseNode->nodeType() == Node::TEXT_NODE || m_baseNode->nodeType() == Node::CDATA_SECTION_NODE)) {
@@ -584,24 +598,33 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
     }
 #endif  // APPLE_CHANGES
 
-	// update the state
+	// adjust the state
 	if (!m_startNode && !m_endNode)
 		m_state = NONE;
-	if (m_startNode == m_endNode && m_startOffset == m_endOffset)
+	else if (m_startNode == m_endNode && m_startOffset == m_endOffset)
 		m_state = CARET;
 	else
 		m_state = RANGE;
+
+    m_needsCaretLayout = true;
     
-    m_startEndValid = true;
+#if EDIT_DEBUG
+    debugPosition();
+#endif
 }
 
-DOMPosition KHTMLSelection::previousCharacterPosition()
+DOMPosition KHTMLSelection::previousCharacterPosition() const
 {
-    if (!startNode())
-        return DOMPosition();
+    return previousCharacterPosition(DOMPosition(startNode(), startOffset()));
+}
 
-	NodeImpl *node = startNode();
-	long offset = startOffset() - 1;
+DOMPosition KHTMLSelection::previousCharacterPosition(const DOMPosition &from)
+{
+    if (!from.node())
+        return from;
+
+	NodeImpl *node = from.node();
+	long offset = from.offset() - 1;
 
     //
     // Look in this renderer
@@ -653,7 +676,7 @@ DOMPosition KHTMLSelection::previousCharacterPosition()
     			 	continue;
     		}
             offset = renderer->caretMaxOffset();
-            if (!renderer->precedesLineBreak())
+            if (renderer->nextEditable() && !renderer->precedesLineBreak())
                 offset--;
             assert(offset >= 0);
             return DOMPosition(renderer->element(), offset);
@@ -662,16 +685,22 @@ DOMPosition KHTMLSelection::previousCharacterPosition()
     }
 
     // can't move the position
-    return DOMPosition(startNode(), startOffset());
+    return from;
+}
+
+
+DOMPosition KHTMLSelection::nextCharacterPosition() const
+{
+    return nextCharacterPosition(DOMPosition(endNode(), endOffset()));
 }
 
-DOMPosition KHTMLSelection::nextCharacterPosition()
+DOMPosition KHTMLSelection::nextCharacterPosition(const DOMPosition &from)
 {
-    if (!endNode())
+    if (!from.node())
         return DOMPosition();
 
-	NodeImpl *node = endNode();
-	long offset = endOffset() + 1;
+ 	NodeImpl *node = from.node();
+ 	long offset = from.offset() + 1;
 
     //
     // Look in this renderer
@@ -746,9 +775,70 @@ DOMPosition KHTMLSelection::nextCharacterPosition()
     }
 
     // can't move the position
-    return DOMPosition(endNode(), endOffset());
+    return from;
 }
 
+bool KHTMLSelection::moveToRenderedContent()
+{
+    if (isEmpty())
+        return false;
+        
+    if (m_state != CARET)
+        return false;
+
+    DOMPosition pos = DOMPosition(startNode(), startOffset());
+    if (inRenderedContent(pos))
+        return true;
+        
+    // not currently rendered, try moving to next
+    DOMPosition next = nextCharacterPosition(pos);
+    if (next != pos) {
+        moveTo(next);
+        return true;
+    }
+
+    // could not be moved to next, try prev
+    DOMPosition prev = previousCharacterPosition(pos);
+    if (prev != pos) {
+        moveTo(prev);
+        return true;
+    }
+    
+    return false;
+}
+
+bool KHTMLSelection::inRenderedContent(const DOMPosition &pos)
+{
+    if (pos.isEmpty())
+        return false;
+        
+ 	long offset = pos.offset();
+
+    RenderObject *renderer = pos.node()->renderer();
+    if (!renderer)
+        return false;
+    
+    if (renderer->isText() && !renderer->isBR()) {
+        RenderText *textRenderer = static_cast<khtml::RenderText *>(renderer);
+        for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+            if (offset >= box->m_start && offset <= box->m_start + box->m_len) {
+                return true;
+            }
+            else if (offset < box->m_start) {
+                // The offset we're looking for is before this node
+                // this means the offset must be in content that is
+                // not rendered. Return false.
+                return false;
+            }
+        }
+    }
+    else if (offset >= renderer->caretMinOffset() && offset <= renderer->caretMaxOffset()) {
+        return true;
+    }
+    
+    return false;
+}
+ 
 bool KHTMLSelection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2) 
 {
 	if (!n1 || !n2) 
@@ -787,7 +877,7 @@ bool KHTMLSelection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2)
         n2 = n2->parentNode();
     }
     // Iterate through the parent's children until n1 or n2 is found
-    n = n1->parentNode()->firstChild();
+    n = n1->parentNode() ? n1->parentNode()->firstChild() : n1->firstChild();
     while (n) {
         if (n == n1) {
             result = true;
@@ -946,7 +1036,7 @@ static bool startAndEndLineNodesIncludingNode(DOM::NodeImpl *node, int offset, K
         if (!lastRunAt (renderNode, selectionPointY, endNode, endOffset))
             return false;
         
-        selection.setSelection(startNode, startOffset, endNode, endOffset);
+        selection.moveTo(startNode, startOffset, endNode, endOffset);
         
         return true;
     }
@@ -1010,7 +1100,7 @@ void KHTMLSelection::debugRenderer(RenderObject *r, bool selected) const
             
             show = show.replace("\n", " ");
             show = show.replace("\r", " ");
-            fprintf(stderr, "==> #text : %s at %d\n", show.latin1(), pos);
+            fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.latin1(), pos);
             fprintf(stderr, "           ");
             for (int i = 0; i < caret; i++)
                 fprintf(stderr, " ");
@@ -1021,7 +1111,7 @@ void KHTMLSelection::debugRenderer(RenderObject *r, bool selected) const
                 text = text.left(max - 3) + "...";
             else
                 text = text.left(max);
-            fprintf(stderr, "    #text : %s\n", text.latin1());
+            fprintf(stderr, "    #text : \"%s\"\n", text.latin1());
         }
     }
 }
diff --git a/WebCore/khtml/editing/selection.h b/WebCore/khtml/editing/selection.h
index e888924..6932b21 100644
--- a/WebCore/khtml/editing/selection.h
+++ b/WebCore/khtml/editing/selection.h
@@ -26,14 +26,9 @@
 #ifndef __khtml_selection_h__
 #define __khtml_selection_h__
 
-#include <qobject.h>
-
 class KHTMLPart;
-class KHTMLPartPrivate;
-class KHTMLView;
 class QPainter;
 class QRect;
-class QTimerEvent;
 
 namespace DOM {
     class DOMPosition;
@@ -45,12 +40,13 @@ namespace khtml {
     class RenderObject;
 }
 
-class KHTMLSelection : public QObject
+class KHTMLSelection
 {
-  Q_OBJECT
-
 public:
     KHTMLSelection();
+    KHTMLSelection(DOM::NodeImpl *node, long offset);
+    KHTMLSelection(const DOM::DOMPosition &);
+    KHTMLSelection(DOM::NodeImpl *startNode, long startOffset, DOM::NodeImpl *endNode, long endOffset);
     KHTMLSelection(const KHTMLSelection &);
     ~KHTMLSelection();
 
@@ -61,41 +57,45 @@ public:
 
 	EState state() const { return m_state; }
 
-    void setSelection(DOM::NodeImpl *node, long offset);
-    void setSelection(const DOM::Range &);
-    void setSelection(const DOM::DOMPosition &);
-    void setSelection(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset);
+    void moveTo(DOM::NodeImpl *node, long offset);
+    void moveTo(const DOM::Range &);
+    void moveTo(const DOM::DOMPosition &);
+    void moveTo(const KHTMLSelection &);
+    void moveTo(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset);
+    bool modify(EAlter, EDirection, ETextElement);
+    void expandToElement(ETextElement);
+    void clear();
+
+    bool moveToRenderedContent();
+    
     void setBase(DOM::NodeImpl *node, long offset);
     void setExtent(DOM::NodeImpl *node, long offset);
-    void expandSelection(ETextElement);
-    bool alterSelection(EAlter, EDirection, ETextElement);
-    void clearSelection();
-    
+
     DOM::NodeImpl *baseNode() const { return m_baseNode; }
     long baseOffset() const { return m_baseOffset; }
 
     DOM::NodeImpl *extentNode() const { return m_extentNode; }
     long extentOffset() const { return m_extentOffset; }
 
-    DOM::NodeImpl *startNode() const;
-    long startOffset() const;
+    DOM::NodeImpl *startNode() const { return m_startNode; }
+    long startOffset() const { return m_startOffset; }
 
-    DOM::NodeImpl *endNode() const;
-    long endOffset() const;
+    DOM::NodeImpl *endNode() const { return m_endNode; }
+    long endOffset() const { return m_endOffset; }
 
-    void setVisible(bool flag=true);
-    bool visible() const { return m_visible; }
-
-    DOM::DOMPosition previousCharacterPosition();
-    DOM::DOMPosition nextCharacterPosition();
+    DOM::DOMPosition previousCharacterPosition() const;
+    static DOM::DOMPosition previousCharacterPosition(const DOM::DOMPosition &from);
+    DOM::DOMPosition nextCharacterPosition() const;
+    static DOM::DOMPosition nextCharacterPosition(const DOM::DOMPosition &from);
         
-    void invalidate();
+    void setNeedsLayout(bool flag=true);
     
     bool isEmpty() const;
+    DOM::Range toRange() const;
+
     
-#ifdef APPLE_CHANGES
-    void paint(QPainter *p, const QRect &rect) const;
-#endif
+    void debugPosition() const;
+    void debugRenderer(khtml::RenderObject *r, bool selected) const;
 
     KHTMLSelection &operator=(const KHTMLSelection &o);
     
@@ -104,35 +104,30 @@ public:
     
     friend class KHTMLPart;
 
-    void debugPosition() const;
-    void debugRenderer(khtml::RenderObject *r, bool selected) const;
-
 private:
-    void setPart(KHTMLPart *part);
-
-    void update();
+    void init();
+    void validate(ETextElement expandTo=CHARACTER);
 
-    void timerEvent(QTimerEvent *e);
-    void repaint(bool immediate=false) const;
+    void layoutCaret();
+    void needsCaretRepaint();
+    QRect getRepaintRect();
+    void paintCaret(QPainter *p, const QRect &rect);
 
 	void setBaseNode(DOM::NodeImpl *);
 	void setBaseOffset(long);
 	void setExtentNode(DOM::NodeImpl *);
 	void setExtentOffset(long);
 
-	void setStart(DOM::NodeImpl *, long);
 	void setStartNode(DOM::NodeImpl *);
 	void setStartOffset(long);
-	void setEnd(DOM::NodeImpl *, long);
 	void setEndNode(DOM::NodeImpl *);
 	void setEndOffset(long);
 
+    bool inRenderedContent(const DOM::DOMPosition &);
     bool nodeIsBeforeNode(DOM::NodeImpl *n1, DOM::NodeImpl *n2);
 
     void calculateStartAndEnd(ETextElement select=CHARACTER);
     
-    KHTMLPart *m_part;            // part for this selection
-
     DOM::NodeImpl *m_baseNode;    // base node for the selection
     long m_baseOffset;            // offset into base node where selection is
     DOM::NodeImpl *m_extentNode;  // extent node for the selection
@@ -145,24 +140,19 @@ private:
 
 	EState m_state;               // the state of the selection
 
-    int m_caretBlinkTimer;        // caret blink frequency timer id
-	
 	int m_caretX;
 	int m_caretY;
 	int m_caretSize;
 
-	bool m_baseIsStart : 1;     // true if base node is before the extent node
-    bool m_caretBlinks : 1;     // true if caret blinks
-    bool m_caretPaint : 1;      // flag used to deal with blinking the caret
-    bool m_visible : 1;         // true if selection is to be displayed at all
-	bool m_startEndValid : 1;   // true if the start and end are valid
+	bool m_baseIsStart : 1;       // true if base node is before the extent node
+	bool m_needsCaretLayout : 1;  // true if the caret position needs to be calculated
 };
 
 
 inline bool operator==(const KHTMLSelection &a, const KHTMLSelection &b)
 {
-    return a.baseNode() == b.baseNode() && a.baseOffset() == b.baseOffset() &&
-        a.extentNode() == b.extentNode() && a.extentOffset() == b.extentOffset();
+    return a.startNode() == b.startNode() && a.startOffset() == b.startOffset() &&
+        a.endNode() == b.endNode() && a.endOffset() == b.endOffset();
 }
 
 inline bool operator!=(const KHTMLSelection &a, const KHTMLSelection &b)
diff --git a/WebCore/khtml/html/html_elementimpl.cpp b/WebCore/khtml/html/html_elementimpl.cpp
index 04b406d..545c38b 100644
--- a/WebCore/khtml/html/html_elementimpl.cpp
+++ b/WebCore/khtml/html/html_elementimpl.cpp
@@ -703,7 +703,7 @@ void HTMLElementImpl::addHTMLAlignment( const DOMString& alignment )
 
 bool HTMLElementImpl::isFocusable() const
 {
-    return isContentEditable();
+    return isContentEditable() && !parent()->isContentEditable();
 }
 
 bool HTMLElementImpl::isContentEditable() const {
diff --git a/WebCore/khtml/khtml_part.cpp b/WebCore/khtml/khtml_part.cpp
index 944552b..a8c15b2 100644
--- a/WebCore/khtml/khtml_part.cpp
+++ b/WebCore/khtml/khtml_part.cpp
@@ -38,6 +38,7 @@
 
 #include "dom/dom_string.h"
 #include "dom/dom_element.h"
+#include "editing/htmlediting.h"
 #include "html/html_documentimpl.h"
 #include "html/html_baseimpl.h"
 #include "html/html_miscimpl.h"
@@ -98,12 +99,15 @@ using namespace DOM;
 #endif
 
 using khtml::Decoder;
+using khtml::EditCommand;
 using khtml::RenderObject;
 using khtml::RenderText;
 using khtml::InlineTextBox;
 
 using KParts::BrowserInterface;
 
+enum { CARET_BLINK_FREQUENCY = 500 };
+
 namespace khtml {
     class PartStyleSheetLoader : public CachedObjectClient
     {
@@ -185,9 +189,6 @@ void KHTMLPart::init( KHTMLView *view, GUIProfile prof )
   d->m_view = view;
   setWidget( d->m_view );
   
-  d->m_selection.setPart(this);
-  d->m_selection.setVisible();
-
 #if !APPLE_CHANGES
   d->m_guiProfile = prof;
 #endif
@@ -2214,10 +2215,9 @@ bool KHTMLPart::findTextNext( const QString &str, bool forward, bool caseSensiti
                   ->posOfChar(d->m_findPos, x, y);
                 d->m_view->setContentsPos(x-50, y-50);
 #endif
-                
-                d->m_selection.setSelection(d->m_findNode, d->m_findPos, d->m_findNode, d->m_findPos + matchLen);
-                d->m_doc->setSelection(d->m_selection);
-                emitSelectionChanged();
+                KHTMLSelection s = selection();
+                s.moveTo(d->m_findNode, d->m_findPos, d->m_findNode, d->m_findPos + matchLen);
+                setSelection(s);
                 return true;
             }
         }
@@ -2442,7 +2442,7 @@ QString KHTMLPart::text(const DOM::Range &r) const
 
 QString KHTMLPart::selectedText() const
 {
-    return text(selection());
+    return text(selection().toRange());
 }
 
 bool KHTMLPart::hasSelection() const
@@ -2450,35 +2450,89 @@ bool KHTMLPart::hasSelection() const
     return !d->m_selection.isEmpty();
 }
 
-DOM::Range KHTMLPart::selection() const
+const KHTMLSelection &KHTMLPart::selection() const
+{
+    return d->m_selection;
+}
+
+void KHTMLPart::setSelection(const KHTMLSelection &s)
 {
-    DOM::Range r = document().createRange();
-    if (hasSelection()) {
-        r.setStart(d->m_selection.startNode(), d->m_selection.startOffset());
-        r.setEnd(d->m_selection.endNode(), d->m_selection.endOffset());
+    if (d->m_selection != s) {
+        d->m_selection = s;
+        notifySelectionChanged();
     }
-    return r;
 }
 
-void KHTMLPart::setSelection(const DOM::Range &r)
+void KHTMLPart::clearSelection()
 {
-    d->m_selection.setSelection(r);
-    d->m_doc->setSelection(d->m_selection);
-    emitSelectionChanged();
+    d->m_selection = KHTMLSelection();
+    notifySelectionChanged();
+}
+
+void KHTMLPart::invalidateSelection()
+{
+    d->m_selection.setNeedsLayout();
+    notifySelectionChanged();
+}
+
+void KHTMLPart::setSelectionVisible(bool flag)
+{
+    if (d->m_caretVisible == flag)
+        return;
+        
+    d->m_caretVisible = flag;
+    notifySelectionChanged();
 }
 
 void KHTMLPart::slotClearSelection()
 {
     bool hadSelection = hasSelection();
-    d->m_selection.clearSelection();
-    d->m_doc->clearSelection();
+    d->m_selection.clear();
     if (hadSelection)
-        emitSelectionChanged();
+        notifySelectionChanged();
 }
 
-KHTMLSelection &KHTMLPart::getKHTMLSelection() const
+void KHTMLPart::notifySelectionChanged()
 {
-    return d->m_selection;
+    // kill any caret blink timer now running
+    if (d->m_caretBlinkTimer >= 0) {
+        killTimer(d->m_caretBlinkTimer);
+        d->m_caretBlinkTimer = -1;
+    }
+
+    // see if a new caret blink timer needs to be started
+    if (d->m_caretVisible && d->m_caretBlinks && 
+        d->m_selection.state() == KHTMLSelection::CARET && d->m_selection.startNode()->isContentEditable()) {
+        d->m_caretBlinkTimer = startTimer(CARET_BLINK_FREQUENCY);
+        d->m_caretPaint = true;
+        d->m_selection.needsCaretRepaint();
+    }
+    else if (d->m_caretPaint) {
+        d->m_caretPaint = false;
+        d->m_selection.needsCaretRepaint();
+    }
+
+    if (d->m_doc)
+        d->m_doc->updateSelection();
+        
+    emitSelectionChanged();
+}
+
+void KHTMLPart::timerEvent(QTimerEvent *e)
+{
+    if (e->timerId() == d->m_caretBlinkTimer && 
+        d->m_caretVisible && 
+        d->m_caretBlinks && 
+        d->m_selection.state() == KHTMLSelection::CARET) {
+        d->m_caretPaint = !d->m_caretPaint;
+        d->m_selection.needsCaretRepaint();
+    }
+}
+
+void KHTMLPart::paintCaret(QPainter *p, const QRect &rect) const
+{
+    if (d->m_caretPaint)
+        d->m_selection.paintCaret(p, rect);
 }
 
 #if !APPLE_CHANGES
@@ -4415,27 +4469,23 @@ void KHTMLPart::handleMousePressEventDoubleClick(khtml::MousePressEvent *event)
     QMouseEvent *mouse = event->qmouseEvent();
     DOM::Node innerNode = event->innerNode();
 
-    d->m_selection.clearSelection();
+    KHTMLSelection selection;
 
     if (mouse->button() == LeftButton && !innerNode.isNull() && innerNode.handle()->renderer()) {
         NodeImpl *node = 0;
         int offset = 0;
         checkSelectionPoint(event, node, offset);
         if (node && (node->nodeType() == Node::TEXT_NODE || node->nodeType() == Node::CDATA_SECTION_NODE)) {
-            d->m_selection.setSelection(node, offset);
-            d->m_selection.expandSelection(KHTMLSelection::WORD);
+            selection.moveTo(node, offset);
+            selection.expandToElement(KHTMLSelection::WORD);
         }
     }
-    
-    if (d->m_selection.state() == KHTMLSelection::CARET) {
-        d->m_doc->clearSelection();
-    }
-    else {
+
+    if (selection.state() != KHTMLSelection::CARET) {
         d->m_textElement = KHTMLSelection::WORD;
-        d->m_doc->setSelection(d->m_selection);
     }
     
-    emitSelectionChanged();
+    setSelection(selection);
     startAutoScroll();
 }
 
@@ -4444,27 +4494,23 @@ void KHTMLPart::handleMousePressEventTripleClick(khtml::MousePressEvent *event)
     QMouseEvent *mouse = event->qmouseEvent();
     DOM::Node innerNode = event->innerNode();
     
-    d->m_selection.clearSelection();
+    KHTMLSelection selection;
     
     if (mouse->button() == LeftButton && !innerNode.isNull() && innerNode.handle()->renderer()) {
-        DOM::NodeImpl* node = 0;
+        NodeImpl* node = 0;
         int offset = 0;
         checkSelectionPoint(event, node, offset);
         if (node && (node->nodeType() == Node::TEXT_NODE || node->nodeType() == Node::CDATA_SECTION_NODE)) {
-            d->m_selection.setSelection(node, offset);
-            d->m_selection.expandSelection(KHTMLSelection::LINE);
+            selection.moveTo(node, offset);
+            selection.expandToElement(KHTMLSelection::LINE);
         }
     }
     
-    if (d->m_selection.state() == KHTMLSelection::CARET) {
-        d->m_doc->clearSelection();
-    }
-    else {
+    if (selection.state() != KHTMLSelection::CARET) {
         d->m_textElement = KHTMLSelection::LINE;
-        d->m_doc->setSelection(d->m_selection);
     }
     
-    emitSelectionChanged();
+    setSelection(selection);
     startAutoScroll();
 }
 
@@ -4476,10 +4522,8 @@ void KHTMLPart::handleMousePressEventSingleClick(khtml::MousePressEvent *event)
     DOM::Node innerNode = event->innerNode();
     
 	if (mouse->button() == LeftButton) {
-		if (innerNode.isNull() || !innerNode.handle()->renderer()) {
-			d->m_selection.clearSelection();
-		}
-		else {
+        KHTMLSelection selection;
+        if (!innerNode.isNull() && innerNode.handle()->renderer()) {
 #if APPLE_CHANGES
 			// Don't restart the selection when the mouse is pressed on an
 			// existing selection so we can allow for text dragging.
@@ -4490,11 +4534,10 @@ void KHTMLPart::handleMousePressEventSingleClick(khtml::MousePressEvent *event)
 			DOM::NodeImpl* node = 0;
 			int offset = 0;
         	checkSelectionPoint(event, node, offset);
-            d->m_selection.setSelection(node, offset);
-			d->m_doc->clearSelection();
+            selection.moveTo(node, offset);
 		}
 
-		emitSelectionChanged();
+        setSelection(selection);
 		startAutoScroll();
 	}
 }
@@ -4701,23 +4744,22 @@ void KHTMLPart::handleMouseMoveEventSelection(khtml::MouseMoveEvent *event)
 
 	// Restart the selection if this is the first mouse move. This work is usually
 	// done in khtmlMousePressEvent, but not if the mouse press was on an existing selection.
-	if (!d->m_mouseMovedSinceLastMousePress) {
+	KHTMLSelection sel = selection();
+    if (!d->m_mouseMovedSinceLastMousePress) {
 		d->m_mouseMovedSinceLastMousePress = true;
-        d->m_selection.setSelection(node, offset);
+        sel.moveTo(node, offset);
 	}
 #endif        
 
-    d->m_selection.setExtent(node, offset);
+    sel.setExtent(node, offset);
 
 #if APPLE_CHANGES
     if (d->m_textElement != KHTMLSelection::CHARACTER) {
-        d->m_selection.expandSelection(d->m_textElement);
+        sel.expandToElement(d->m_textElement);
     }
 #endif    
 
-    if (!d->m_selection.isEmpty()) {
-        d->m_doc->setSelection(d->m_selection);
-    }
+    setSelection(sel);
         
 #endif // KHTML_NO_SELECTION
 }
@@ -4779,17 +4821,14 @@ void KHTMLPart::khtmlMouseReleaseEvent( khtml::MouseReleaseEvent *event )
 		d->m_dragStartPos.y() == event->qmouseEvent()->y() &&
 		d->m_selection.state() == KHTMLSelection::RANGE &&
         d->m_textElement == KHTMLSelection::CHARACTER) {
+            KHTMLSelection selection;
             if (isEditingAtCaret()) {
                 NodeImpl *node = 0;
                 int offset = 0;
                 checkSelectionPoint(event, node, offset);
-                d->m_selection.setSelection(node, offset);
-                d->m_doc->setSelection(d->m_selection);
-            }
-            else {
-                d->m_selection.clearSelection();
-                d->m_doc->clearSelection();
+                selection.moveTo(node, offset);
             }
+            setSelection(selection);
 	}
 #endif
 
@@ -4807,8 +4846,6 @@ void KHTMLPart::khtmlMouseReleaseEvent( khtml::MouseReleaseEvent *event )
     cb->setSelectionMode(false);
 #endif // QT_NO_CLIPBOARD
 
-    emitSelectionChanged();
-
 #endif // KHTML_NO_SELECTION
 }
 
@@ -4997,9 +5034,8 @@ void KHTMLPart::selectAll()
     return;
   Q_ASSERT(first->renderer());
   Q_ASSERT(last->renderer());
-  d->m_selection.setSelection(first, 0, last, last->nodeValue().length());
-  d->m_doc->setSelection(d->m_selection);
-  emitSelectionChanged();
+  KHTMLSelection selection(first, 0, last, last->nodeValue().length());
+  setSelection(selection);
 }
 
 bool KHTMLPart::isEditingAtCaret() const
@@ -5013,6 +5049,88 @@ bool KHTMLPart::isEditingAtCaret() const
     return false;
 }
 
+int KHTMLPart::applyCommand(EditCommand *cmd)
+{
+    int result = cmd->apply();
+    if (result) {
+        return result;
+    }
+    
+    // clear redo commands
+    QPtrListIterator<EditCommand> it(d->m_redoEditCommands);
+    for (; it.current(); ++it)
+        delete it.current();
+    d->m_redoEditCommands.clear();
+
+    // add to undo commands
+    d->m_undoEditCommands.append(cmd);
+#if APPLE_CHANGES
+    KWQ(this)->registerCommandForUndo(cmd->cookie());
+#endif
+
+    return khtml::EditResultOK;
+}
+
+#if APPLE_CHANGES
+int KHTMLPart::undoRedoEditing(int cookie)
+{
+    EditCommand *undoCommand = d->m_undoEditCommands.last();
+    if (undoCommand && undoCommand->cookie() == cookie)
+        return undoEditing();
+    
+    EditCommand *redoCommand = d->m_redoEditCommands.last();
+    if (redoCommand && redoCommand->cookie() == cookie)
+        return redoEditing();
+
+    return khtml::EditResultFailed;
+}
+#endif
+
+int KHTMLPart::undoEditing()
+{
+    EditCommand *cmd = d->m_undoEditCommands.last();
+    if (!cmd) {
+        return khtml::EditResultFailed;
+    }
+
+    int result = cmd->unapply();
+    if (result) {
+        return result;
+    }
+
+    d->m_undoEditCommands.removeLast();
+    d->m_redoEditCommands.append(cmd);
+
+#if APPLE_CHANGES
+    KWQ(this)->registerCommandForUndo(cmd->cookie());
+#endif
+
+    return khtml::EditResultOK;
+}
+
+int KHTMLPart::redoEditing()
+{
+    EditCommand *cmd = d->m_redoEditCommands.last();
+    if (!cmd) {
+        return khtml::EditResultFailed;
+    }
+
+    int result = cmd->reapply();
+    if (result) {
+        return result;
+    }
+
+    d->m_redoEditCommands.removeLast();
+    d->m_undoEditCommands.append(cmd);
+
+#if APPLE_CHANGES
+    KWQ(this)->registerCommandForUndo(cmd->cookie());
+#endif
+
+    return khtml::EditResultOK;
+}
+
+
 #if !APPLE_CHANGES
 
 bool KHTMLPart::checkLinkSecurity(const KURL &linkURL,const QString &message, const QString &button)
diff --git a/WebCore/khtml/khtml_part.h b/WebCore/khtml/khtml_part.h
index 37e4255..f10dc5f 100644
--- a/WebCore/khtml/khtml_part.h
+++ b/WebCore/khtml/khtml_part.h
@@ -80,6 +80,7 @@ namespace khtml
   class CachedObject;
   class RenderWidget;
   class CSSStyleSelector;
+  class EditCommand;
 };
 
 namespace KJS {
@@ -563,12 +564,32 @@ public:
   /**
    * Returns the selected part of the HTML.
    */
-  DOM::Range selection() const;
+  const KHTMLSelection &selection() const;
 
   /**
    * Sets the current selection.
    */
-  void setSelection(const DOM::Range &);
+  void setSelection(const KHTMLSelection &);
+
+  /**
+   * Clears the current selection.
+   */
+  void clearSelection();
+
+  /**
+   * Invalidates the current selection.
+   */
+  void invalidateSelection();
+
+  /**
+   * Controls the visibility of the selection.
+   */
+  void setSelectionVisible(bool flag=true);
+
+  /**
+   * Paints the caret.
+   */
+  void paintCaret(QPainter *p, const QRect &rect) const;
 
   /**
    * Returns the text for a part of the document.
@@ -591,17 +612,37 @@ public:
   void selectAll();
 
   /**
-   * Returns the caret.
-   */
-  KHTMLSelection &getKHTMLSelection() const;
-
-  /**
    * Returns whether editing is enabled at the current caret
    * position.
    */
   bool isEditingAtCaret() const;
 
   /**
+   * Applies the give edit command.
+   */
+  int applyCommand(khtml::EditCommand *);
+
+#if APPLE_CHANGES
+  /**
+   * Performs an undo or redo of the most previous edit
+   * by examining the undo and redo command list and
+   * matching the top item against the cookie passed in.
+   * A hack, but it helps us to hook into Cocoa undo/redo.
+   */
+  int undoRedoEditing(int cookie);
+#endif
+
+  /**
+   * Performs an undo of the edit.
+   */
+  int undoEditing();
+
+  /**
+   * Performs a redo of the edit.
+   */
+  int redoEditing();
+
+  /**
    * Convenience method to show the document's view.
    *
    * Equivalent to widget()->show() or view()->show() .
@@ -1038,7 +1079,17 @@ private:
   /**
    * @internal
    */
+  void notifySelectionChanged();
+
+  /**
+   * @internal
+   */
   void emitSelectionChanged();
+
+  /**
+   * @internal
+   */
+  void timerEvent(QTimerEvent *);
   
   /**
    * @internal
@@ -1134,6 +1185,7 @@ private:
 
   KHTMLPartPrivate *d;
   friend class KHTMLPartPrivate;
+  friend class KHTMLSelection;
 
 #if APPLE_CHANGES
 public:  
diff --git a/WebCore/khtml/khtml_selection.cpp b/WebCore/khtml/khtml_selection.cpp
index c631e6d..91e3f1c 100644
--- a/WebCore/khtml/khtml_selection.cpp
+++ b/WebCore/khtml/khtml_selection.cpp
@@ -61,8 +61,6 @@ using khtml::InlineTextBox;
 using khtml::RenderObject;
 using khtml::RenderText;
 
-enum { CARET_BLINK_FREQUENCY = 500 };
-
 #if APPLE_CHANGES
 static void findWordBoundary(QChar *chars, int len, int position, int *start, int *end);
 static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset);
@@ -71,35 +69,94 @@ static bool startAndEndLineNodesIncludingNode(DOM::NodeImpl *node, int offset, K
 #endif
 
 
-KHTMLSelection::KHTMLSelection() 
-	: QObject(),
-	  m_part(0),
-	  m_baseNode(0), m_baseOffset(0), m_extentNode(0), m_extentOffset(0),
-	  m_startNode(0), m_startOffset(0), m_endNode(0), m_endOffset(0),
-	  m_state(NONE), m_caretBlinkTimer(0),
-      m_baseIsStart(true), m_caretBlinks(true), m_caretPaint(false), 
-      m_visible(false), m_startEndValid(false)
+KHTMLSelection::KHTMLSelection()
+{
+    init();
+}
+
+KHTMLSelection::KHTMLSelection(NodeImpl *node, long offset)
 {
+    init();
+
+	setBaseNode(node);
+	setExtentNode(node);
+	setBaseOffset(offset);
+	setExtentOffset(offset);
+
+    validate();
+}
+
+KHTMLSelection::KHTMLSelection(const DOM::DOMPosition &pos)
+{
+    init();
+
+	setBaseNode(pos.node());
+	setExtentNode(pos.node());
+	setBaseOffset(pos.offset());
+	setExtentOffset(pos.offset());
+
+    validate();
+}
+
+KHTMLSelection::KHTMLSelection(NodeImpl *baseNode, long baseOffset, NodeImpl *endNode, long endOffset)
+{
+    init();
+
+	setBaseNode(baseNode);
+	setExtentNode(endNode);
+	setBaseOffset(baseOffset);
+	setExtentOffset(endOffset);
+
+    validate();
 }
 
 KHTMLSelection::KHTMLSelection(const KHTMLSelection &o)
-	: QObject(),
-	  m_part(o.m_part),
-	  m_baseNode(0), m_baseOffset(0), m_extentNode(0), m_extentOffset(0),
-	  m_startNode(0), m_startOffset(0), m_endNode(0), m_endOffset(0)
 {
-    setBase(o.baseNode(), o.baseOffset());
-    setExtent(o.extentNode(), o.extentOffset());
-    setStart(o.startNode(), o.startOffset());
-    setEnd(o.endNode(), o.endOffset());
-
-	m_state = o.m_state;
-	m_caretBlinkTimer = o.m_caretBlinkTimer;
-	m_baseIsStart = o.m_baseIsStart;
-	m_caretBlinks = o.m_caretBlinks;
-	m_caretPaint = true;
-	m_visible = o.m_visible;
-    m_startEndValid = true;
+    init();
+    
+	setBaseNode(o.baseNode());
+	setExtentNode(o.extentNode());
+	setBaseOffset(o.baseOffset());
+	setExtentOffset(o.extentOffset());
+
+	setStartNode(o.startNode());
+	setEndNode(o.endNode());
+	setStartOffset(o.startOffset());
+	setEndOffset(o.endOffset());
+
+    m_state = o.m_state;
+
+    m_baseIsStart = o.m_baseIsStart;
+    m_needsCaretLayout = o.m_needsCaretLayout;
+
+    // Only copy the coordinates over if the other object
+    // has had a layout, otherwise keep the current
+    // coordinates. This prevents drawing artifacts from
+    // remaining when the caret is painted and then moves,
+    // and the old rectangle needs to be repainted.
+    if (!m_needsCaretLayout) {
+        m_caretX = o.m_caretX;
+        m_caretY = o.m_caretY;
+        m_caretSize = o.m_caretSize;
+    }
+}
+
+void KHTMLSelection::init()
+{
+    m_baseNode = 0;
+    m_baseOffset = 0;
+    m_extentNode = 0; 
+    m_extentOffset = 0;
+    m_startNode = 0;
+    m_startOffset = 0;
+    m_endNode = 0;
+    m_endOffset = 0;
+    m_state = NONE; 
+    m_caretX = 0;
+    m_caretY = 0;
+    m_caretSize = 0;
+    m_baseIsStart = true;
+    m_needsCaretLayout = true;
 }
 
 KHTMLSelection::~KHTMLSelection()
@@ -108,77 +165,74 @@ KHTMLSelection::~KHTMLSelection()
         m_baseNode->deref();
     if (m_extentNode)
         m_extentNode->deref();
+    if (m_startNode)
+        m_startNode->deref();
+    if (m_endNode)
+        m_endNode->deref();
 }
 
 KHTMLSelection &KHTMLSelection::operator=(const KHTMLSelection &o)
 {
-    m_part = o.m_part;
+	setBaseNode(o.baseNode());
+	setExtentNode(o.extentNode());
+	setBaseOffset(o.baseOffset());
+	setExtentOffset(o.extentOffset());
+
+	setStartNode(o.startNode());
+	setEndNode(o.endNode());
+	setStartOffset(o.startOffset());
+	setEndOffset(o.endOffset());
+
+    m_state = o.m_state;
+
+    m_baseIsStart = o.m_baseIsStart;
+    m_needsCaretLayout = o.m_needsCaretLayout;
+    
+    // Only copy the coordinates over if the other object
+    // has had a layout, otherwise keep the current
+    // coordinates. This prevents drawing artifacts from
+    // remaining when the caret is painted and then moves,
+    // and the old rectangle needs to be repainted.
+    if (!m_needsCaretLayout) {
+        m_caretX = o.m_caretX;
+        m_caretY = o.m_caretY;
+        m_caretSize = o.m_caretSize;
+    }
     
-    setBase(o.baseNode(), o.baseOffset());
-    setExtent(o.extentNode(), o.extentOffset());
-    setStart(o.startNode(), o.startOffset());
-    setEnd(o.endNode(), o.endOffset());
-
-	m_state = o.m_state;
-	m_caretBlinkTimer = o.m_caretBlinkTimer;
-	m_baseIsStart = o.m_baseIsStart;
-	m_caretBlinks = o.m_caretBlinks;
-	m_caretPaint = true;
-	m_visible = o.m_visible;
-    m_startEndValid = true;
     return *this;
 }
 
-void KHTMLSelection::setSelection(DOM::NodeImpl *node, long offset)
+void KHTMLSelection::moveTo(DOM::NodeImpl *node, long offset)
 {
-	setBaseNode(node);
-	setExtentNode(node);
-	setBaseOffset(offset);
-	setExtentOffset(offset);
-	update();
-#if EDIT_DEBUG
-    debugPosition();
-#endif
+    moveTo(node, offset, node, offset);
 }
 
-void KHTMLSelection::setSelection(const DOM::Range &r)
+void KHTMLSelection::moveTo(const DOM::Range &r)
 {
-	setSelection(r.startContainer().handle(), r.startOffset(), 
+	moveTo(r.startContainer().handle(), r.startOffset(), 
 		r.endContainer().handle(), r.endOffset());
 }
 
-void KHTMLSelection::setSelection(const DOM::DOMPosition &pos)
+void KHTMLSelection::moveTo(const DOM::DOMPosition &pos)
 {
-	setSelection(pos.node(), pos.offset());
+	moveTo(pos.node(), pos.offset());
 }
 
-void KHTMLSelection::setSelection(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset)
+void KHTMLSelection::moveTo(const KHTMLSelection &o)
+{
+	moveTo(o.baseNode(), o.baseOffset(), o.extentNode(), o.extentOffset());
+}
+
+void KHTMLSelection::moveTo(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset)
 {
 	setBaseNode(baseNode);
 	setExtentNode(extentNode);
 	setBaseOffset(baseOffset);
 	setExtentOffset(extentOffset);
-	update();
-#if EDIT_DEBUG
-    debugPosition();
-#endif
-}
-
-void KHTMLSelection::setBase(DOM::NodeImpl *node, long offset)
-{
-	setBaseNode(node);
-	setBaseOffset(offset);
-	update();
+	validate();
 }
 
-void KHTMLSelection::setExtent(DOM::NodeImpl *node, long offset)
-{
-	setExtentNode(node);
-	setExtentOffset(offset);
-	update();
-}
-
-bool KHTMLSelection::alterSelection(EAlter alter, EDirection dir, ETextElement elem)
+bool KHTMLSelection::modify(EAlter alter, EDirection dir, ETextElement elem)
 {
     DOMPosition pos;
     
@@ -211,187 +265,137 @@ bool KHTMLSelection::alterSelection(EAlter alter, EDirection dir, ETextElement e
         return false;
     
     if (alter == MOVE)
-        setSelection(pos.node(), pos.offset());
+        moveTo(pos.node(), pos.offset());
     else // alter == EXTEND
         setExtent(pos.node(), pos.offset());
     
     return true;
 }
 
-void KHTMLSelection::clearSelection()
+void KHTMLSelection::expandToElement(ETextElement select)
+{
+    validate(select);
+}
+
+void KHTMLSelection::clear()
 {
 	setBaseNode(0);
 	setExtentNode(0);
 	setBaseOffset(0);
 	setExtentOffset(0);
-	update();
-}
-
-NodeImpl *KHTMLSelection::startNode() const
-{ 
-    return m_startNode;
+	validate();
 }
 
-long KHTMLSelection::startOffset() const
-{ 
-    return m_startOffset;
+void KHTMLSelection::setBase(DOM::NodeImpl *node, long offset)
+{
+	setBaseNode(node);
+	setBaseOffset(offset);
+	validate();
 }
 
-NodeImpl *KHTMLSelection::endNode() const 
+void KHTMLSelection::setExtent(DOM::NodeImpl *node, long offset)
 {
-    return m_endNode;
+	setExtentNode(node);
+	setExtentOffset(offset);
+	validate();
 }
 
-long KHTMLSelection::endOffset() const 
-{ 
-    return m_endOffset;
+void KHTMLSelection::setNeedsLayout(bool flag)
+{
+    m_needsCaretLayout = flag;
 }
 
-void KHTMLSelection::setVisible(bool flag)
+bool KHTMLSelection::isEmpty() const
 {
-    m_visible = flag;
-    update();
+    return m_baseNode == 0 && m_extentNode == 0;
 }
 
-void KHTMLSelection::invalidate()
+Range KHTMLSelection::toRange() const
 {
-    update();
+    if (isEmpty())
+        return Range();
+
+    return Range(Node(startNode()), startOffset(), Node(endNode()), endOffset());
 }
 
-void KHTMLSelection::update()
+void KHTMLSelection::layoutCaret()
 {
-    // make sure we do not have a dangling start or end
-	if (!m_baseNode && !m_extentNode) {
-        setBaseOffset(0);
-        setExtentOffset(0);
-        m_baseIsStart = true;
+    if (isEmpty() || !startNode()->renderer()) {
+        m_caretX = m_caretY = m_caretSize = 0;
     }
-	else if (!m_baseNode) {
-		setBaseNode(m_extentNode);
-		setBaseOffset(m_extentOffset);
-        m_baseIsStart = true;
-	}
-	else if (!m_extentNode) {
-		setExtentNode(m_baseNode);
-		setExtentOffset(m_baseOffset);
-        m_baseIsStart = true;
-	}
     else {
-        // adjust m_baseIsStart as needed
-        if (m_baseNode == m_extentNode) {
-            if (m_baseOffset > m_extentOffset)
-                m_baseIsStart = false;
-            else 
-                m_baseIsStart = true;
-        }
-        else if (nodeIsBeforeNode(m_baseNode, m_extentNode))
-            m_baseIsStart = true;
-        else
-            m_baseIsStart = false;
+        int w;
+        startNode()->renderer()->caretPos(startOffset(), true, m_caretX, m_caretY, w, m_caretSize);
     }
 
-    // update start and end
-    m_startEndValid = false;
-    calculateStartAndEnd();
-    
-    // update the blink timer
-    if (m_caretBlinkTimer >= 0)
-        killTimer(m_caretBlinkTimer);
-    if (m_visible && m_state == CARET && m_caretBlinks)
-        m_caretBlinkTimer = startTimer(CARET_BLINK_FREQUENCY);
-    else
-        m_caretBlinkTimer = -1;
+    m_needsCaretLayout = false;
+}
 
-    // short-circuit if not visible
-    if (!m_visible) {
-        if (m_caretPaint) {
-            m_caretPaint = false;
-            repaint();
-        }
-        return;
-    }
+QRect KHTMLSelection::getRepaintRect()
+{
+    // EDIT FIXME: fudge a bit to make sure we don't leave behind artifacts
+    return QRect(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2);
+}
 
-    // short-circuit if not CARET state
-	if (m_state != CARET)
-		return;
+void KHTMLSelection::needsCaretRepaint()
+{
+    if (isEmpty())
+        return;
 
-    // calculate the new caret rendering position
-    int oldX = m_caretX;   
-    int oldY = m_caretY;   
-    int oldSize = m_caretSize;
-    
-    int newX = 0;
-    int newY = 0;
-    int newSize = 0;
-    
-    NodeImpl *node = startNode();
-    if (node && node->renderer()) {
-        int w;
-        node->renderer()->caretPos(startOffset(), true, newX, newY, w, newSize);
-    }
+    if (!startNode()->getDocument())
+        return;
 
-    // repaint the old position if necessary
-    // prevents two carets from ever being drawn
-    if (m_caretPaint && (oldX != newX || oldY != newY || oldSize != newSize)) {
-        repaint();
-    }
+    KHTMLView *v = startNode()->getDocument()->view();
+    if (!v)
+        return;
 
-    // update caret rendering position
-    m_caretX = newX;
-    m_caretY = newY;
-    m_caretSize = newSize;
-    
-    // paint the caret if it is visible
-    if (m_visible && m_caretSize != 0) {
-        m_caretPaint = true;
-        repaint();
+    if (m_needsCaretLayout) {
+        // repaint old position and calculate new position
+        v->updateContents(getRepaintRect(), false);
+        layoutCaret();
+        
+        // EDIT FIXME: This is an unfortunate hack.
+        // Basically, we can't trust this layout position since we 
+        // can't guarantee that the check to see if we are in unrendered 
+        // content will work at this point. We may have to wait for
+        // a layout and re-render of the document to happen. So, resetting this
+        // flag will cause another caret layout to happen the first time
+        // that we try to paint the caret after this call. That one will work since
+        // it happens after the document has accounted for any editing
+        // changes which may have been done.
+        // And, we need to leave this layout here so the caret moves right 
+        // away after clicking.
+        m_needsCaretLayout = true;
     }
+    v->updateContents(getRepaintRect(), false);
 }
 
-bool KHTMLSelection::isEmpty() const
+void KHTMLSelection::paintCaret(QPainter *p, const QRect &rect)
 {
-    return m_baseNode == 0 && m_extentNode == 0;
-}
+    if (isEmpty())
+        return;
 
-#ifdef APPLE_CHANGES
-void KHTMLSelection::paint(QPainter *p, const QRect &rect) const
-{
-    if (!m_caretPaint || m_state != CARET)
+    if (m_state != CARET)
         return;
 
-    QRect pos(m_caretX, m_caretY, 1, m_caretSize);
-    if (pos.intersects(rect)) {
+    if (m_needsCaretLayout) {
+        DOMPosition pos = DOMPosition(startNode(), startOffset());
+        if (!inRenderedContent(pos)) {
+            moveToRenderedContent();
+        }
+        layoutCaret();
+    }
+
+    QRect caretRect(m_caretX, m_caretY, 1, m_caretSize);
+    if (caretRect.intersects(rect)) {
         QPen pen = p->pen();
-        pen.setStyle(SolidLine);
+        pen.setStyle(Qt::SolidLine);
         pen.setColor(Qt::black);
         pen.setWidth(1);
         p->setPen(pen);
-        p->drawLine(pos.left(), pos.top(), pos.left(), pos.bottom());
+        p->drawLine(caretRect.left(), caretRect.top(), caretRect.left(), caretRect.bottom());
     }
 }
-#endif
-
-void KHTMLSelection::setPart(KHTMLPart *part)
-{
-    m_part = part;
-}
-
-void KHTMLSelection::timerEvent(QTimerEvent *e)
-{
-    if (e->timerId() == m_caretBlinkTimer && m_visible) {
-        m_caretPaint = !m_caretPaint;
-        repaint();
-    }
-}
-
-void KHTMLSelection::repaint(bool immediate) const
-{
-    KHTMLView *v = m_part->view();
-    if (!v)
-        return;
-    // EDIT FIXME: fudge a bit to make sure we don't leave behind artifacts
-    v->updateContents(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2, immediate);
-}
 
 void KHTMLSelection::setBaseNode(DOM::NodeImpl *node)
 {
@@ -431,12 +435,6 @@ void KHTMLSelection::setExtentOffset(long offset)
 	m_extentOffset = offset;
 }
 
-void KHTMLSelection::setStart(DOM::NodeImpl *node, long offset)
-{
-    setStartNode(node);
-    setStartOffset(offset);
-}
-
 void KHTMLSelection::setStartNode(DOM::NodeImpl *node)
 {
 	if (m_startNode == node)
@@ -456,12 +454,6 @@ void KHTMLSelection::setStartOffset(long offset)
 	m_startOffset = offset;
 }
 
-void KHTMLSelection::setEnd(DOM::NodeImpl *node, long offset)
-{
-    setEndNode(node);
-    setEndOffset(offset);
-}
-
 void KHTMLSelection::setEndNode(DOM::NodeImpl *node)
 {
 	if (m_endNode == node)
@@ -481,17 +473,39 @@ void KHTMLSelection::setEndOffset(long offset)
 	m_endOffset = offset;
 }
 
-void KHTMLSelection::expandSelection(ETextElement select)
-{
-    m_startEndValid = false;
-    calculateStartAndEnd(select);
-}
-
-void KHTMLSelection::calculateStartAndEnd(ETextElement select)
+void KHTMLSelection::validate(ETextElement expandTo)
 {
-    if (m_startEndValid)
-        return;
+    // make sure we do not have a dangling start or end
+	if (!m_baseNode && !m_extentNode) {
+        setBaseOffset(0);
+        setExtentOffset(0);
+        m_baseIsStart = true;
+    }
+	else if (!m_baseNode) {
+		setBaseNode(m_extentNode);
+		setBaseOffset(m_extentOffset);
+        m_baseIsStart = true;
+	}
+	else if (!m_extentNode) {
+		setExtentNode(m_baseNode);
+		setExtentOffset(m_baseOffset);
+        m_baseIsStart = true;
+	}
+    else {
+        // adjust m_baseIsStart as needed
+        if (m_baseNode == m_extentNode) {
+            if (m_baseOffset > m_extentOffset)
+                m_baseIsStart = false;
+            else 
+                m_baseIsStart = true;
+        }
+        else if (nodeIsBeforeNode(m_baseNode, m_extentNode))
+            m_baseIsStart = true;
+        else
+            m_baseIsStart = false;
+    }
 
+    // calculate the correct start and end positions
 #if !APPLE_CHANGES
     if (m_baseIsStart) {
         setStartNode(m_baseNode);
@@ -506,7 +520,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
         setEndOffset(m_baseOffset);
     }
 #else
-    if (select == CHARACTER) {
+    if (expandTo == CHARACTER) {
         if (m_baseIsStart) {
             setStartNode(m_baseNode);
             setStartOffset(m_baseOffset);
@@ -520,7 +534,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
             setEndOffset(m_baseOffset);
         }
     }
-    else if (select == WORD) {
+    else if (expandTo == WORD) {
         int baseStartOffset = m_baseOffset;
         int baseEndOffset = m_baseOffset;
         int extentStartOffset = m_extentOffset;
@@ -550,7 +564,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
             setEndOffset(baseEndOffset);
         }
     }
-    else {  // select == LINE
+    else {  // expandTo == LINE
         KHTMLSelection baseSelection = *this;
         KHTMLSelection extentSelection = *this;
         if (m_baseNode && (m_baseNode->nodeType() == Node::TEXT_NODE || m_baseNode->nodeType() == Node::CDATA_SECTION_NODE)) {
@@ -584,24 +598,33 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
     }
 #endif  // APPLE_CHANGES
 
-	// update the state
+	// adjust the state
 	if (!m_startNode && !m_endNode)
 		m_state = NONE;
-	if (m_startNode == m_endNode && m_startOffset == m_endOffset)
+	else if (m_startNode == m_endNode && m_startOffset == m_endOffset)
 		m_state = CARET;
 	else
 		m_state = RANGE;
+
+    m_needsCaretLayout = true;
     
-    m_startEndValid = true;
+#if EDIT_DEBUG
+    debugPosition();
+#endif
 }
 
-DOMPosition KHTMLSelection::previousCharacterPosition()
+DOMPosition KHTMLSelection::previousCharacterPosition() const
 {
-    if (!startNode())
-        return DOMPosition();
+    return previousCharacterPosition(DOMPosition(startNode(), startOffset()));
+}
 
-	NodeImpl *node = startNode();
-	long offset = startOffset() - 1;
+DOMPosition KHTMLSelection::previousCharacterPosition(const DOMPosition &from)
+{
+    if (!from.node())
+        return from;
+
+	NodeImpl *node = from.node();
+	long offset = from.offset() - 1;
 
     //
     // Look in this renderer
@@ -653,7 +676,7 @@ DOMPosition KHTMLSelection::previousCharacterPosition()
     			 	continue;
     		}
             offset = renderer->caretMaxOffset();
-            if (!renderer->precedesLineBreak())
+            if (renderer->nextEditable() && !renderer->precedesLineBreak())
                 offset--;
             assert(offset >= 0);
             return DOMPosition(renderer->element(), offset);
@@ -662,16 +685,22 @@ DOMPosition KHTMLSelection::previousCharacterPosition()
     }
 
     // can't move the position
-    return DOMPosition(startNode(), startOffset());
+    return from;
+}
+
+
+DOMPosition KHTMLSelection::nextCharacterPosition() const
+{
+    return nextCharacterPosition(DOMPosition(endNode(), endOffset()));
 }
 
-DOMPosition KHTMLSelection::nextCharacterPosition()
+DOMPosition KHTMLSelection::nextCharacterPosition(const DOMPosition &from)
 {
-    if (!endNode())
+    if (!from.node())
         return DOMPosition();
 
-	NodeImpl *node = endNode();
-	long offset = endOffset() + 1;
+ 	NodeImpl *node = from.node();
+ 	long offset = from.offset() + 1;
 
     //
     // Look in this renderer
@@ -746,9 +775,70 @@ DOMPosition KHTMLSelection::nextCharacterPosition()
     }
 
     // can't move the position
-    return DOMPosition(endNode(), endOffset());
+    return from;
 }
 
+bool KHTMLSelection::moveToRenderedContent()
+{
+    if (isEmpty())
+        return false;
+        
+    if (m_state != CARET)
+        return false;
+
+    DOMPosition pos = DOMPosition(startNode(), startOffset());
+    if (inRenderedContent(pos))
+        return true;
+        
+    // not currently rendered, try moving to next
+    DOMPosition next = nextCharacterPosition(pos);
+    if (next != pos) {
+        moveTo(next);
+        return true;
+    }
+
+    // could not be moved to next, try prev
+    DOMPosition prev = previousCharacterPosition(pos);
+    if (prev != pos) {
+        moveTo(prev);
+        return true;
+    }
+    
+    return false;
+}
+
+bool KHTMLSelection::inRenderedContent(const DOMPosition &pos)
+{
+    if (pos.isEmpty())
+        return false;
+        
+ 	long offset = pos.offset();
+
+    RenderObject *renderer = pos.node()->renderer();
+    if (!renderer)
+        return false;
+    
+    if (renderer->isText() && !renderer->isBR()) {
+        RenderText *textRenderer = static_cast<khtml::RenderText *>(renderer);
+        for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+            if (offset >= box->m_start && offset <= box->m_start + box->m_len) {
+                return true;
+            }
+            else if (offset < box->m_start) {
+                // The offset we're looking for is before this node
+                // this means the offset must be in content that is
+                // not rendered. Return false.
+                return false;
+            }
+        }
+    }
+    else if (offset >= renderer->caretMinOffset() && offset <= renderer->caretMaxOffset()) {
+        return true;
+    }
+    
+    return false;
+}
+ 
 bool KHTMLSelection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2) 
 {
 	if (!n1 || !n2) 
@@ -787,7 +877,7 @@ bool KHTMLSelection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2)
         n2 = n2->parentNode();
     }
     // Iterate through the parent's children until n1 or n2 is found
-    n = n1->parentNode()->firstChild();
+    n = n1->parentNode() ? n1->parentNode()->firstChild() : n1->firstChild();
     while (n) {
         if (n == n1) {
             result = true;
@@ -946,7 +1036,7 @@ static bool startAndEndLineNodesIncludingNode(DOM::NodeImpl *node, int offset, K
         if (!lastRunAt (renderNode, selectionPointY, endNode, endOffset))
             return false;
         
-        selection.setSelection(startNode, startOffset, endNode, endOffset);
+        selection.moveTo(startNode, startOffset, endNode, endOffset);
         
         return true;
     }
@@ -1010,7 +1100,7 @@ void KHTMLSelection::debugRenderer(RenderObject *r, bool selected) const
             
             show = show.replace("\n", " ");
             show = show.replace("\r", " ");
-            fprintf(stderr, "==> #text : %s at %d\n", show.latin1(), pos);
+            fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.latin1(), pos);
             fprintf(stderr, "           ");
             for (int i = 0; i < caret; i++)
                 fprintf(stderr, " ");
@@ -1021,7 +1111,7 @@ void KHTMLSelection::debugRenderer(RenderObject *r, bool selected) const
                 text = text.left(max - 3) + "...";
             else
                 text = text.left(max);
-            fprintf(stderr, "    #text : %s\n", text.latin1());
+            fprintf(stderr, "    #text : \"%s\"\n", text.latin1());
         }
     }
 }
diff --git a/WebCore/khtml/khtml_selection.h b/WebCore/khtml/khtml_selection.h
index e888924..6932b21 100644
--- a/WebCore/khtml/khtml_selection.h
+++ b/WebCore/khtml/khtml_selection.h
@@ -26,14 +26,9 @@
 #ifndef __khtml_selection_h__
 #define __khtml_selection_h__
 
-#include <qobject.h>
-
 class KHTMLPart;
-class KHTMLPartPrivate;
-class KHTMLView;
 class QPainter;
 class QRect;
-class QTimerEvent;
 
 namespace DOM {
     class DOMPosition;
@@ -45,12 +40,13 @@ namespace khtml {
     class RenderObject;
 }
 
-class KHTMLSelection : public QObject
+class KHTMLSelection
 {
-  Q_OBJECT
-
 public:
     KHTMLSelection();
+    KHTMLSelection(DOM::NodeImpl *node, long offset);
+    KHTMLSelection(const DOM::DOMPosition &);
+    KHTMLSelection(DOM::NodeImpl *startNode, long startOffset, DOM::NodeImpl *endNode, long endOffset);
     KHTMLSelection(const KHTMLSelection &);
     ~KHTMLSelection();
 
@@ -61,41 +57,45 @@ public:
 
 	EState state() const { return m_state; }
 
-    void setSelection(DOM::NodeImpl *node, long offset);
-    void setSelection(const DOM::Range &);
-    void setSelection(const DOM::DOMPosition &);
-    void setSelection(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset);
+    void moveTo(DOM::NodeImpl *node, long offset);
+    void moveTo(const DOM::Range &);
+    void moveTo(const DOM::DOMPosition &);
+    void moveTo(const KHTMLSelection &);
+    void moveTo(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset);
+    bool modify(EAlter, EDirection, ETextElement);
+    void expandToElement(ETextElement);
+    void clear();
+
+    bool moveToRenderedContent();
+    
     void setBase(DOM::NodeImpl *node, long offset);
     void setExtent(DOM::NodeImpl *node, long offset);
-    void expandSelection(ETextElement);
-    bool alterSelection(EAlter, EDirection, ETextElement);
-    void clearSelection();
-    
+
     DOM::NodeImpl *baseNode() const { return m_baseNode; }
     long baseOffset() const { return m_baseOffset; }
 
     DOM::NodeImpl *extentNode() const { return m_extentNode; }
     long extentOffset() const { return m_extentOffset; }
 
-    DOM::NodeImpl *startNode() const;
-    long startOffset() const;
+    DOM::NodeImpl *startNode() const { return m_startNode; }
+    long startOffset() const { return m_startOffset; }
 
-    DOM::NodeImpl *endNode() const;
-    long endOffset() const;
+    DOM::NodeImpl *endNode() const { return m_endNode; }
+    long endOffset() const { return m_endOffset; }
 
-    void setVisible(bool flag=true);
-    bool visible() const { return m_visible; }
-
-    DOM::DOMPosition previousCharacterPosition();
-    DOM::DOMPosition nextCharacterPosition();
+    DOM::DOMPosition previousCharacterPosition() const;
+    static DOM::DOMPosition previousCharacterPosition(const DOM::DOMPosition &from);
+    DOM::DOMPosition nextCharacterPosition() const;
+    static DOM::DOMPosition nextCharacterPosition(const DOM::DOMPosition &from);
         
-    void invalidate();
+    void setNeedsLayout(bool flag=true);
     
     bool isEmpty() const;
+    DOM::Range toRange() const;
+
     
-#ifdef APPLE_CHANGES
-    void paint(QPainter *p, const QRect &rect) const;
-#endif
+    void debugPosition() const;
+    void debugRenderer(khtml::RenderObject *r, bool selected) const;
 
     KHTMLSelection &operator=(const KHTMLSelection &o);
     
@@ -104,35 +104,30 @@ public:
     
     friend class KHTMLPart;
 
-    void debugPosition() const;
-    void debugRenderer(khtml::RenderObject *r, bool selected) const;
-
 private:
-    void setPart(KHTMLPart *part);
-
-    void update();
+    void init();
+    void validate(ETextElement expandTo=CHARACTER);
 
-    void timerEvent(QTimerEvent *e);
-    void repaint(bool immediate=false) const;
+    void layoutCaret();
+    void needsCaretRepaint();
+    QRect getRepaintRect();
+    void paintCaret(QPainter *p, const QRect &rect);
 
 	void setBaseNode(DOM::NodeImpl *);
 	void setBaseOffset(long);
 	void setExtentNode(DOM::NodeImpl *);
 	void setExtentOffset(long);
 
-	void setStart(DOM::NodeImpl *, long);
 	void setStartNode(DOM::NodeImpl *);
 	void setStartOffset(long);
-	void setEnd(DOM::NodeImpl *, long);
 	void setEndNode(DOM::NodeImpl *);
 	void setEndOffset(long);
 
+    bool inRenderedContent(const DOM::DOMPosition &);
     bool nodeIsBeforeNode(DOM::NodeImpl *n1, DOM::NodeImpl *n2);
 
     void calculateStartAndEnd(ETextElement select=CHARACTER);
     
-    KHTMLPart *m_part;            // part for this selection
-
     DOM::NodeImpl *m_baseNode;    // base node for the selection
     long m_baseOffset;            // offset into base node where selection is
     DOM::NodeImpl *m_extentNode;  // extent node for the selection
@@ -145,24 +140,19 @@ private:
 
 	EState m_state;               // the state of the selection
 
-    int m_caretBlinkTimer;        // caret blink frequency timer id
-	
 	int m_caretX;
 	int m_caretY;
 	int m_caretSize;
 
-	bool m_baseIsStart : 1;     // true if base node is before the extent node
-    bool m_caretBlinks : 1;     // true if caret blinks
-    bool m_caretPaint : 1;      // flag used to deal with blinking the caret
-    bool m_visible : 1;         // true if selection is to be displayed at all
-	bool m_startEndValid : 1;   // true if the start and end are valid
+	bool m_baseIsStart : 1;       // true if base node is before the extent node
+	bool m_needsCaretLayout : 1;  // true if the caret position needs to be calculated
 };
 
 
 inline bool operator==(const KHTMLSelection &a, const KHTMLSelection &b)
 {
-    return a.baseNode() == b.baseNode() && a.baseOffset() == b.baseOffset() &&
-        a.extentNode() == b.extentNode() && a.extentOffset() == b.extentOffset();
+    return a.startNode() == b.startNode() && a.startOffset() == b.startOffset() &&
+        a.endNode() == b.endNode() && a.endOffset() == b.endOffset();
 }
 
 inline bool operator!=(const KHTMLSelection &a, const KHTMLSelection &b)
diff --git a/WebCore/khtml/khtmlpart_p.h b/WebCore/khtml/khtmlpart_p.h
index e5dac6b..7d5e4d4 100644
--- a/WebCore/khtml/khtmlpart_p.h
+++ b/WebCore/khtml/khtmlpart_p.h
@@ -82,6 +82,7 @@ namespace khtml
     bool m_bNotify;
   };
 
+  class EditCommand;
 };
 
 class FrameList : public QValueList<khtml::ChildFrame>
@@ -120,7 +121,6 @@ public:
     m_bCleared = false;
     m_zoomFactor = 100;
     m_bDnd = true;
-    m_extendAtEnd = true;
 #if !APPLE_CHANGES
     m_linkCursor = KCursor::handCursor();
 #endif
@@ -153,6 +153,10 @@ public:
     m_onlyLocalReferences = false;
 
     m_inEditMode = DOM::FlagNone;
+    m_caretBlinkTimer = 0;
+    m_caretVisible = true;
+    m_caretBlinks = true;
+    m_caretPaint = true;
 
     m_metaRefreshEnabled = true;
     m_bHTTPRefresh = false;
@@ -210,6 +214,16 @@ public:
 #ifndef Q_WS_QWS
     delete m_javaContext;
 #endif
+    
+    QPtrListIterator<khtml::EditCommand> undos(m_undoEditCommands);
+    for (; undos.current(); ++undos) {
+        delete undos.current();
+    }
+    QPtrListIterator<khtml::EditCommand> redos(m_redoEditCommands);
+    for (; redos.current(); ++redos) {
+        delete redos.current();
+    }
+
   }
 
   FrameList m_frames;
@@ -344,11 +358,15 @@ public:
   KHTMLSelection::ETextElement m_textElement;
   bool m_mouseMovedSinceLastMousePress:1;
 #endif
-  KHTMLSelection m_selection;
   QString m_overURL;
   QString m_overURLTarget;
 
-  bool m_extendAtEnd:1;
+  KHTMLSelection m_selection;
+  int m_caretBlinkTimer;
+
+  bool m_caretVisible:1;
+  bool m_caretBlinks:1;
+  bool m_caretPaint:1;
   bool m_bDnd:1;
   bool m_bFirstData:1;
   bool m_bClearing:1;
@@ -357,6 +375,8 @@ public:
   bool m_focusNodeRestored:1;
 
   TristateFlag m_inEditMode;
+  QPtrList<khtml::EditCommand> m_undoEditCommands;
+  QPtrList<khtml::EditCommand> m_redoEditCommands;
 
   int m_focusNodeNumber;
 
diff --git a/WebCore/khtml/khtmlview.cpp b/WebCore/khtml/khtmlview.cpp
index 7b5f47c..97d0450 100644
--- a/WebCore/khtml/khtmlview.cpp
+++ b/WebCore/khtml/khtmlview.cpp
@@ -331,7 +331,8 @@ void KHTMLView::clear()
 //    viewport()->erase();
 
     setStaticBackground(false);
-    m_part->getKHTMLSelection().clearSelection();
+    
+    m_part->clearSelection();
 
     d->reset();
     killTimers();
@@ -597,7 +598,7 @@ void KHTMLView::layout()
 
     root->layout();
 
-    m_part->getKHTMLSelection().invalidate();
+    m_part->invalidateSelection();
         
     //kdDebug( 6000 ) << "TIME: layout() dt=" << qt.elapsed() << endl;
    
@@ -1742,14 +1743,14 @@ void KHTMLView::dropEvent( QDropEvent *ev )
 
 void KHTMLView::focusInEvent( QFocusEvent *e )
 {
-    m_part->getKHTMLSelection().setVisible();
+    m_part->setSelectionVisible();
     QScrollView::focusInEvent( e );
 }
 
 void KHTMLView::focusOutEvent( QFocusEvent *e )
 {
     m_part->stopAutoScroll();
-    m_part->getKHTMLSelection().setVisible(false);
+    m_part->setSelectionVisible(false);
     QScrollView::focusOutEvent( e );
 }
 
diff --git a/WebCore/khtml/rendering/render_block.cpp b/WebCore/khtml/rendering/render_block.cpp
index 15766de..fb71c30 100644
--- a/WebCore/khtml/rendering/render_block.cpp
+++ b/WebCore/khtml/rendering/render_block.cpp
@@ -1244,11 +1244,11 @@ void RenderBlock::paintObject(QPainter *p, int _x, int _y,
         then paint the caret.
     */
     if (paintAction == PaintActionForeground) {
-        const KHTMLSelection &s = document()->part()->getKHTMLSelection();
+        const KHTMLSelection &s = document()->part()->selection();
         NodeImpl *baseNode = s.baseNode();
         RenderObject *renderer = baseNode ? baseNode->renderer() : 0;
         if (renderer && renderer->containingBlock() == this && baseNode->isContentEditable()) {
-            s.paint(p, QRect(_x, _y, _w, _h));
+            document()->part()->paintCaret(p, QRect(_x, _y, _w, _h));
         }
     }
     
diff --git a/WebCore/khtml/xml/dom_docimpl.cpp b/WebCore/khtml/xml/dom_docimpl.cpp
index 100cb3f..1cdd2c5 100644
--- a/WebCore/khtml/xml/dom_docimpl.cpp
+++ b/WebCore/khtml/xml/dom_docimpl.cpp
@@ -1196,34 +1196,30 @@ void DocumentImpl::setVisuallyOrdered()
         m_render->style()->setVisuallyOrdered(true);
 }
 
-void DocumentImpl::setSelection(NodeImpl* s, int sp, NodeImpl* e, int ep)
+void DocumentImpl::updateSelection()
 {
-    if ( m_render )
-        static_cast<RenderCanvas*>(m_render)->setSelection(s->renderer(),sp,e->renderer(),ep);
-}
-
-void DocumentImpl::setSelection(KHTMLSelection &s)
-{
-    if (m_render) {
+    if (!m_render)
+        return;
+    
+    RenderCanvas *canvas = static_cast<RenderCanvas*>(m_render);
+    KHTMLSelection s = part()->selection();
+    if (s.isEmpty() || s.state() == KHTMLSelection::CARET) {
+        canvas->clearSelection();
+    }
+    else {
         RenderObject *startRenderer = s.startNode() ? s.startNode()->renderer() : 0;
         RenderObject *endRenderer = s.endNode() ? s.endNode()->renderer() : 0;
         static_cast<RenderCanvas*>(m_render)->setSelection(startRenderer, s.startOffset(), endRenderer, s.endOffset());
     }
 }
 
-void DocumentImpl::clearSelection()
-{
-    if ( m_render )
-        static_cast<RenderCanvas*>(m_render)->clearSelection();
-}
-
 void DocumentImpl::deleteSelection()
 {
-    KHTMLSelection &selection = part()->getKHTMLSelection();
-    if (!selection.isEmpty()) {
-        clearSelection();
-        Range range(selection.startNode(), selection.startOffset(), selection.endNode(), selection.endOffset());
+    KHTMLSelection s = part()->selection();
+    if (!s.isEmpty()) {
+        Range range(s.startNode(), s.startOffset(), s.endNode(), s.endOffset());
         range.deleteContents();
+        part()->clearSelection();
     }
 }
 
@@ -1231,7 +1227,7 @@ void DocumentImpl::pasteHTMLString(const QString &HTMLString)
 {	
 	deleteSelection();
 	
-	KHTMLSelection &selection = part()->getKHTMLSelection();
+	KHTMLSelection selection = part()->selection();
 	DOM::NodeImpl *startNode = selection.startNode();
     long startOffset = selection.startOffset();
     DOM::NodeImpl *endNode = selection.endNode();
@@ -1260,7 +1256,7 @@ void DocumentImpl::pasteHTMLString(const QString &HTMLString)
 		// Simple text paste. Add the text to the text node with the caret.
 		textNode->insertData(startOffset, static_cast<TextImpl *>(firstChild)->data(), exceptionCode);
 		finalOffset = startOffset + static_cast<TextImpl *>(firstChild)->length();
-		selection.setSelection(textNode, finalOffset);
+		selection.moveTo(textNode, finalOffset);
 	} else {
 		// HTML tree paste.
 		DOM::NodeImpl *parent = startNode->parentNode();
@@ -1292,10 +1288,10 @@ void DocumentImpl::pasteHTMLString(const QString &HTMLString)
 			child = nextChild;
 		}
 		finalOffset = child->isTextNode() ? static_cast<TextImpl *>(child)->length() : 1;
-		selection.setSelection(child, finalOffset);
+		selection.moveTo(child, finalOffset);
 	}
 	
-	setSelection(selection);
+	part()->setSelection(selection);
 }
 
 Tokenizer *DocumentImpl::createTokenizer()
diff --git a/WebCore/khtml/xml/dom_docimpl.h b/WebCore/khtml/xml/dom_docimpl.h
index ab91dbc..360964e 100644
--- a/WebCore/khtml/xml/dom_docimpl.h
+++ b/WebCore/khtml/xml/dom_docimpl.h
@@ -263,11 +263,9 @@ public:
     // to get visually ordered hebrew and arabic pages right
     void setVisuallyOrdered();
 
-    void setSelection(NodeImpl* s, int sp, NodeImpl* e, int ep);
-    void setSelection(KHTMLSelection &);
-    void clearSelection();
 	void deleteSelection();
-	
+    void updateSelection();
+    
 	void pasteHTMLString(const QString &HTMLString);
 
     void open();
diff --git a/WebCore/khtml/xml/dom_elementimpl.cpp b/WebCore/khtml/xml/dom_elementimpl.cpp
index a3b06c7..00c6b28 100644
--- a/WebCore/khtml/xml/dom_elementimpl.cpp
+++ b/WebCore/khtml/xml/dom_elementimpl.cpp
@@ -383,14 +383,18 @@ void ElementImpl::defaultEventHandler(EventImpl *evt)
         else if (k->keyIdentifier() == "Right") {
             KHTMLPart *part = getDocument()->part();
             if (part) {
-                part->getKHTMLSelection().alterSelection(KHTMLSelection::MOVE, KHTMLSelection::FORWARD, KHTMLSelection::CHARACTER);
+                KHTMLSelection s = part->selection();
+                s.modify(KHTMLSelection::MOVE, KHTMLSelection::FORWARD, KHTMLSelection::CHARACTER);
+                part->setSelection(s);
                 evt->setDefaultHandled();
             }
         }
         else if (k->keyIdentifier() == "Left") {
             KHTMLPart *part = getDocument()->part();
             if (part) {
-                part->getKHTMLSelection().alterSelection(KHTMLSelection::MOVE, KHTMLSelection::BACKWARD, KHTMLSelection::CHARACTER);
+                KHTMLSelection s = part->selection();
+                s.modify(KHTMLSelection::MOVE, KHTMLSelection::BACKWARD, KHTMLSelection::CHARACTER);
+                part->setSelection(s);
                 evt->setDefaultHandled();
             }
         }
@@ -404,11 +408,13 @@ void ElementImpl::defaultEventHandler(EventImpl *evt)
             QString text(k->qKeyEvent()->text());
             cmd = new InputTextCommand(getDocument(), text);
         }
-        if (cmd && cmd->apply()) {
-            evt->setDefaultHandled();
-            // EDIT FIXME: until undo is hooked up, the command has no place to go
-            // just delete
-            delete cmd;
+        if (cmd) {
+            KHTMLPart *part = getDocument()->part();
+            int result = EditResultNoActionTaken;
+            if (part)
+                result = part->applyCommand(cmd);
+            if (result == EditResultOK)
+                evt->setDefaultHandled();
         }
     }
     NodeBaseImpl::defaultEventHandler(evt);
diff --git a/WebCore/khtml/xml/dom_selection.cpp b/WebCore/khtml/xml/dom_selection.cpp
index c631e6d..91e3f1c 100644
--- a/WebCore/khtml/xml/dom_selection.cpp
+++ b/WebCore/khtml/xml/dom_selection.cpp
@@ -61,8 +61,6 @@ using khtml::InlineTextBox;
 using khtml::RenderObject;
 using khtml::RenderText;
 
-enum { CARET_BLINK_FREQUENCY = 500 };
-
 #if APPLE_CHANGES
 static void findWordBoundary(QChar *chars, int len, int position, int *start, int *end);
 static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset);
@@ -71,35 +69,94 @@ static bool startAndEndLineNodesIncludingNode(DOM::NodeImpl *node, int offset, K
 #endif
 
 
-KHTMLSelection::KHTMLSelection() 
-	: QObject(),
-	  m_part(0),
-	  m_baseNode(0), m_baseOffset(0), m_extentNode(0), m_extentOffset(0),
-	  m_startNode(0), m_startOffset(0), m_endNode(0), m_endOffset(0),
-	  m_state(NONE), m_caretBlinkTimer(0),
-      m_baseIsStart(true), m_caretBlinks(true), m_caretPaint(false), 
-      m_visible(false), m_startEndValid(false)
+KHTMLSelection::KHTMLSelection()
+{
+    init();
+}
+
+KHTMLSelection::KHTMLSelection(NodeImpl *node, long offset)
 {
+    init();
+
+	setBaseNode(node);
+	setExtentNode(node);
+	setBaseOffset(offset);
+	setExtentOffset(offset);
+
+    validate();
+}
+
+KHTMLSelection::KHTMLSelection(const DOM::DOMPosition &pos)
+{
+    init();
+
+	setBaseNode(pos.node());
+	setExtentNode(pos.node());
+	setBaseOffset(pos.offset());
+	setExtentOffset(pos.offset());
+
+    validate();
+}
+
+KHTMLSelection::KHTMLSelection(NodeImpl *baseNode, long baseOffset, NodeImpl *endNode, long endOffset)
+{
+    init();
+
+	setBaseNode(baseNode);
+	setExtentNode(endNode);
+	setBaseOffset(baseOffset);
+	setExtentOffset(endOffset);
+
+    validate();
 }
 
 KHTMLSelection::KHTMLSelection(const KHTMLSelection &o)
-	: QObject(),
-	  m_part(o.m_part),
-	  m_baseNode(0), m_baseOffset(0), m_extentNode(0), m_extentOffset(0),
-	  m_startNode(0), m_startOffset(0), m_endNode(0), m_endOffset(0)
 {
-    setBase(o.baseNode(), o.baseOffset());
-    setExtent(o.extentNode(), o.extentOffset());
-    setStart(o.startNode(), o.startOffset());
-    setEnd(o.endNode(), o.endOffset());
-
-	m_state = o.m_state;
-	m_caretBlinkTimer = o.m_caretBlinkTimer;
-	m_baseIsStart = o.m_baseIsStart;
-	m_caretBlinks = o.m_caretBlinks;
-	m_caretPaint = true;
-	m_visible = o.m_visible;
-    m_startEndValid = true;
+    init();
+    
+	setBaseNode(o.baseNode());
+	setExtentNode(o.extentNode());
+	setBaseOffset(o.baseOffset());
+	setExtentOffset(o.extentOffset());
+
+	setStartNode(o.startNode());
+	setEndNode(o.endNode());
+	setStartOffset(o.startOffset());
+	setEndOffset(o.endOffset());
+
+    m_state = o.m_state;
+
+    m_baseIsStart = o.m_baseIsStart;
+    m_needsCaretLayout = o.m_needsCaretLayout;
+
+    // Only copy the coordinates over if the other object
+    // has had a layout, otherwise keep the current
+    // coordinates. This prevents drawing artifacts from
+    // remaining when the caret is painted and then moves,
+    // and the old rectangle needs to be repainted.
+    if (!m_needsCaretLayout) {
+        m_caretX = o.m_caretX;
+        m_caretY = o.m_caretY;
+        m_caretSize = o.m_caretSize;
+    }
+}
+
+void KHTMLSelection::init()
+{
+    m_baseNode = 0;
+    m_baseOffset = 0;
+    m_extentNode = 0; 
+    m_extentOffset = 0;
+    m_startNode = 0;
+    m_startOffset = 0;
+    m_endNode = 0;
+    m_endOffset = 0;
+    m_state = NONE; 
+    m_caretX = 0;
+    m_caretY = 0;
+    m_caretSize = 0;
+    m_baseIsStart = true;
+    m_needsCaretLayout = true;
 }
 
 KHTMLSelection::~KHTMLSelection()
@@ -108,77 +165,74 @@ KHTMLSelection::~KHTMLSelection()
         m_baseNode->deref();
     if (m_extentNode)
         m_extentNode->deref();
+    if (m_startNode)
+        m_startNode->deref();
+    if (m_endNode)
+        m_endNode->deref();
 }
 
 KHTMLSelection &KHTMLSelection::operator=(const KHTMLSelection &o)
 {
-    m_part = o.m_part;
+	setBaseNode(o.baseNode());
+	setExtentNode(o.extentNode());
+	setBaseOffset(o.baseOffset());
+	setExtentOffset(o.extentOffset());
+
+	setStartNode(o.startNode());
+	setEndNode(o.endNode());
+	setStartOffset(o.startOffset());
+	setEndOffset(o.endOffset());
+
+    m_state = o.m_state;
+
+    m_baseIsStart = o.m_baseIsStart;
+    m_needsCaretLayout = o.m_needsCaretLayout;
+    
+    // Only copy the coordinates over if the other object
+    // has had a layout, otherwise keep the current
+    // coordinates. This prevents drawing artifacts from
+    // remaining when the caret is painted and then moves,
+    // and the old rectangle needs to be repainted.
+    if (!m_needsCaretLayout) {
+        m_caretX = o.m_caretX;
+        m_caretY = o.m_caretY;
+        m_caretSize = o.m_caretSize;
+    }
     
-    setBase(o.baseNode(), o.baseOffset());
-    setExtent(o.extentNode(), o.extentOffset());
-    setStart(o.startNode(), o.startOffset());
-    setEnd(o.endNode(), o.endOffset());
-
-	m_state = o.m_state;
-	m_caretBlinkTimer = o.m_caretBlinkTimer;
-	m_baseIsStart = o.m_baseIsStart;
-	m_caretBlinks = o.m_caretBlinks;
-	m_caretPaint = true;
-	m_visible = o.m_visible;
-    m_startEndValid = true;
     return *this;
 }
 
-void KHTMLSelection::setSelection(DOM::NodeImpl *node, long offset)
+void KHTMLSelection::moveTo(DOM::NodeImpl *node, long offset)
 {
-	setBaseNode(node);
-	setExtentNode(node);
-	setBaseOffset(offset);
-	setExtentOffset(offset);
-	update();
-#if EDIT_DEBUG
-    debugPosition();
-#endif
+    moveTo(node, offset, node, offset);
 }
 
-void KHTMLSelection::setSelection(const DOM::Range &r)
+void KHTMLSelection::moveTo(const DOM::Range &r)
 {
-	setSelection(r.startContainer().handle(), r.startOffset(), 
+	moveTo(r.startContainer().handle(), r.startOffset(), 
 		r.endContainer().handle(), r.endOffset());
 }
 
-void KHTMLSelection::setSelection(const DOM::DOMPosition &pos)
+void KHTMLSelection::moveTo(const DOM::DOMPosition &pos)
 {
-	setSelection(pos.node(), pos.offset());
+	moveTo(pos.node(), pos.offset());
 }
 
-void KHTMLSelection::setSelection(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset)
+void KHTMLSelection::moveTo(const KHTMLSelection &o)
+{
+	moveTo(o.baseNode(), o.baseOffset(), o.extentNode(), o.extentOffset());
+}
+
+void KHTMLSelection::moveTo(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset)
 {
 	setBaseNode(baseNode);
 	setExtentNode(extentNode);
 	setBaseOffset(baseOffset);
 	setExtentOffset(extentOffset);
-	update();
-#if EDIT_DEBUG
-    debugPosition();
-#endif
-}
-
-void KHTMLSelection::setBase(DOM::NodeImpl *node, long offset)
-{
-	setBaseNode(node);
-	setBaseOffset(offset);
-	update();
+	validate();
 }
 
-void KHTMLSelection::setExtent(DOM::NodeImpl *node, long offset)
-{
-	setExtentNode(node);
-	setExtentOffset(offset);
-	update();
-}
-
-bool KHTMLSelection::alterSelection(EAlter alter, EDirection dir, ETextElement elem)
+bool KHTMLSelection::modify(EAlter alter, EDirection dir, ETextElement elem)
 {
     DOMPosition pos;
     
@@ -211,187 +265,137 @@ bool KHTMLSelection::alterSelection(EAlter alter, EDirection dir, ETextElement e
         return false;
     
     if (alter == MOVE)
-        setSelection(pos.node(), pos.offset());
+        moveTo(pos.node(), pos.offset());
     else // alter == EXTEND
         setExtent(pos.node(), pos.offset());
     
     return true;
 }
 
-void KHTMLSelection::clearSelection()
+void KHTMLSelection::expandToElement(ETextElement select)
+{
+    validate(select);
+}
+
+void KHTMLSelection::clear()
 {
 	setBaseNode(0);
 	setExtentNode(0);
 	setBaseOffset(0);
 	setExtentOffset(0);
-	update();
-}
-
-NodeImpl *KHTMLSelection::startNode() const
-{ 
-    return m_startNode;
+	validate();
 }
 
-long KHTMLSelection::startOffset() const
-{ 
-    return m_startOffset;
+void KHTMLSelection::setBase(DOM::NodeImpl *node, long offset)
+{
+	setBaseNode(node);
+	setBaseOffset(offset);
+	validate();
 }
 
-NodeImpl *KHTMLSelection::endNode() const 
+void KHTMLSelection::setExtent(DOM::NodeImpl *node, long offset)
 {
-    return m_endNode;
+	setExtentNode(node);
+	setExtentOffset(offset);
+	validate();
 }
 
-long KHTMLSelection::endOffset() const 
-{ 
-    return m_endOffset;
+void KHTMLSelection::setNeedsLayout(bool flag)
+{
+    m_needsCaretLayout = flag;
 }
 
-void KHTMLSelection::setVisible(bool flag)
+bool KHTMLSelection::isEmpty() const
 {
-    m_visible = flag;
-    update();
+    return m_baseNode == 0 && m_extentNode == 0;
 }
 
-void KHTMLSelection::invalidate()
+Range KHTMLSelection::toRange() const
 {
-    update();
+    if (isEmpty())
+        return Range();
+
+    return Range(Node(startNode()), startOffset(), Node(endNode()), endOffset());
 }
 
-void KHTMLSelection::update()
+void KHTMLSelection::layoutCaret()
 {
-    // make sure we do not have a dangling start or end
-	if (!m_baseNode && !m_extentNode) {
-        setBaseOffset(0);
-        setExtentOffset(0);
-        m_baseIsStart = true;
+    if (isEmpty() || !startNode()->renderer()) {
+        m_caretX = m_caretY = m_caretSize = 0;
     }
-	else if (!m_baseNode) {
-		setBaseNode(m_extentNode);
-		setBaseOffset(m_extentOffset);
-        m_baseIsStart = true;
-	}
-	else if (!m_extentNode) {
-		setExtentNode(m_baseNode);
-		setExtentOffset(m_baseOffset);
-        m_baseIsStart = true;
-	}
     else {
-        // adjust m_baseIsStart as needed
-        if (m_baseNode == m_extentNode) {
-            if (m_baseOffset > m_extentOffset)
-                m_baseIsStart = false;
-            else 
-                m_baseIsStart = true;
-        }
-        else if (nodeIsBeforeNode(m_baseNode, m_extentNode))
-            m_baseIsStart = true;
-        else
-            m_baseIsStart = false;
+        int w;
+        startNode()->renderer()->caretPos(startOffset(), true, m_caretX, m_caretY, w, m_caretSize);
     }
 
-    // update start and end
-    m_startEndValid = false;
-    calculateStartAndEnd();
-    
-    // update the blink timer
-    if (m_caretBlinkTimer >= 0)
-        killTimer(m_caretBlinkTimer);
-    if (m_visible && m_state == CARET && m_caretBlinks)
-        m_caretBlinkTimer = startTimer(CARET_BLINK_FREQUENCY);
-    else
-        m_caretBlinkTimer = -1;
+    m_needsCaretLayout = false;
+}
 
-    // short-circuit if not visible
-    if (!m_visible) {
-        if (m_caretPaint) {
-            m_caretPaint = false;
-            repaint();
-        }
-        return;
-    }
+QRect KHTMLSelection::getRepaintRect()
+{
+    // EDIT FIXME: fudge a bit to make sure we don't leave behind artifacts
+    return QRect(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2);
+}
 
-    // short-circuit if not CARET state
-	if (m_state != CARET)
-		return;
+void KHTMLSelection::needsCaretRepaint()
+{
+    if (isEmpty())
+        return;
 
-    // calculate the new caret rendering position
-    int oldX = m_caretX;   
-    int oldY = m_caretY;   
-    int oldSize = m_caretSize;
-    
-    int newX = 0;
-    int newY = 0;
-    int newSize = 0;
-    
-    NodeImpl *node = startNode();
-    if (node && node->renderer()) {
-        int w;
-        node->renderer()->caretPos(startOffset(), true, newX, newY, w, newSize);
-    }
+    if (!startNode()->getDocument())
+        return;
 
-    // repaint the old position if necessary
-    // prevents two carets from ever being drawn
-    if (m_caretPaint && (oldX != newX || oldY != newY || oldSize != newSize)) {
-        repaint();
-    }
+    KHTMLView *v = startNode()->getDocument()->view();
+    if (!v)
+        return;
 
-    // update caret rendering position
-    m_caretX = newX;
-    m_caretY = newY;
-    m_caretSize = newSize;
-    
-    // paint the caret if it is visible
-    if (m_visible && m_caretSize != 0) {
-        m_caretPaint = true;
-        repaint();
+    if (m_needsCaretLayout) {
+        // repaint old position and calculate new position
+        v->updateContents(getRepaintRect(), false);
+        layoutCaret();
+        
+        // EDIT FIXME: This is an unfortunate hack.
+        // Basically, we can't trust this layout position since we 
+        // can't guarantee that the check to see if we are in unrendered 
+        // content will work at this point. We may have to wait for
+        // a layout and re-render of the document to happen. So, resetting this
+        // flag will cause another caret layout to happen the first time
+        // that we try to paint the caret after this call. That one will work since
+        // it happens after the document has accounted for any editing
+        // changes which may have been done.
+        // And, we need to leave this layout here so the caret moves right 
+        // away after clicking.
+        m_needsCaretLayout = true;
     }
+    v->updateContents(getRepaintRect(), false);
 }
 
-bool KHTMLSelection::isEmpty() const
+void KHTMLSelection::paintCaret(QPainter *p, const QRect &rect)
 {
-    return m_baseNode == 0 && m_extentNode == 0;
-}
+    if (isEmpty())
+        return;
 
-#ifdef APPLE_CHANGES
-void KHTMLSelection::paint(QPainter *p, const QRect &rect) const
-{
-    if (!m_caretPaint || m_state != CARET)
+    if (m_state != CARET)
         return;
 
-    QRect pos(m_caretX, m_caretY, 1, m_caretSize);
-    if (pos.intersects(rect)) {
+    if (m_needsCaretLayout) {
+        DOMPosition pos = DOMPosition(startNode(), startOffset());
+        if (!inRenderedContent(pos)) {
+            moveToRenderedContent();
+        }
+        layoutCaret();
+    }
+
+    QRect caretRect(m_caretX, m_caretY, 1, m_caretSize);
+    if (caretRect.intersects(rect)) {
         QPen pen = p->pen();
-        pen.setStyle(SolidLine);
+        pen.setStyle(Qt::SolidLine);
         pen.setColor(Qt::black);
         pen.setWidth(1);
         p->setPen(pen);
-        p->drawLine(pos.left(), pos.top(), pos.left(), pos.bottom());
+        p->drawLine(caretRect.left(), caretRect.top(), caretRect.left(), caretRect.bottom());
     }
 }
-#endif
-
-void KHTMLSelection::setPart(KHTMLPart *part)
-{
-    m_part = part;
-}
-
-void KHTMLSelection::timerEvent(QTimerEvent *e)
-{
-    if (e->timerId() == m_caretBlinkTimer && m_visible) {
-        m_caretPaint = !m_caretPaint;
-        repaint();
-    }
-}
-
-void KHTMLSelection::repaint(bool immediate) const
-{
-    KHTMLView *v = m_part->view();
-    if (!v)
-        return;
-    // EDIT FIXME: fudge a bit to make sure we don't leave behind artifacts
-    v->updateContents(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2, immediate);
-}
 
 void KHTMLSelection::setBaseNode(DOM::NodeImpl *node)
 {
@@ -431,12 +435,6 @@ void KHTMLSelection::setExtentOffset(long offset)
 	m_extentOffset = offset;
 }
 
-void KHTMLSelection::setStart(DOM::NodeImpl *node, long offset)
-{
-    setStartNode(node);
-    setStartOffset(offset);
-}
-
 void KHTMLSelection::setStartNode(DOM::NodeImpl *node)
 {
 	if (m_startNode == node)
@@ -456,12 +454,6 @@ void KHTMLSelection::setStartOffset(long offset)
 	m_startOffset = offset;
 }
 
-void KHTMLSelection::setEnd(DOM::NodeImpl *node, long offset)
-{
-    setEndNode(node);
-    setEndOffset(offset);
-}
-
 void KHTMLSelection::setEndNode(DOM::NodeImpl *node)
 {
 	if (m_endNode == node)
@@ -481,17 +473,39 @@ void KHTMLSelection::setEndOffset(long offset)
 	m_endOffset = offset;
 }
 
-void KHTMLSelection::expandSelection(ETextElement select)
-{
-    m_startEndValid = false;
-    calculateStartAndEnd(select);
-}
-
-void KHTMLSelection::calculateStartAndEnd(ETextElement select)
+void KHTMLSelection::validate(ETextElement expandTo)
 {
-    if (m_startEndValid)
-        return;
+    // make sure we do not have a dangling start or end
+	if (!m_baseNode && !m_extentNode) {
+        setBaseOffset(0);
+        setExtentOffset(0);
+        m_baseIsStart = true;
+    }
+	else if (!m_baseNode) {
+		setBaseNode(m_extentNode);
+		setBaseOffset(m_extentOffset);
+        m_baseIsStart = true;
+	}
+	else if (!m_extentNode) {
+		setExtentNode(m_baseNode);
+		setExtentOffset(m_baseOffset);
+        m_baseIsStart = true;
+	}
+    else {
+        // adjust m_baseIsStart as needed
+        if (m_baseNode == m_extentNode) {
+            if (m_baseOffset > m_extentOffset)
+                m_baseIsStart = false;
+            else 
+                m_baseIsStart = true;
+        }
+        else if (nodeIsBeforeNode(m_baseNode, m_extentNode))
+            m_baseIsStart = true;
+        else
+            m_baseIsStart = false;
+    }
 
+    // calculate the correct start and end positions
 #if !APPLE_CHANGES
     if (m_baseIsStart) {
         setStartNode(m_baseNode);
@@ -506,7 +520,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
         setEndOffset(m_baseOffset);
     }
 #else
-    if (select == CHARACTER) {
+    if (expandTo == CHARACTER) {
         if (m_baseIsStart) {
             setStartNode(m_baseNode);
             setStartOffset(m_baseOffset);
@@ -520,7 +534,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
             setEndOffset(m_baseOffset);
         }
     }
-    else if (select == WORD) {
+    else if (expandTo == WORD) {
         int baseStartOffset = m_baseOffset;
         int baseEndOffset = m_baseOffset;
         int extentStartOffset = m_extentOffset;
@@ -550,7 +564,7 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
             setEndOffset(baseEndOffset);
         }
     }
-    else {  // select == LINE
+    else {  // expandTo == LINE
         KHTMLSelection baseSelection = *this;
         KHTMLSelection extentSelection = *this;
         if (m_baseNode && (m_baseNode->nodeType() == Node::TEXT_NODE || m_baseNode->nodeType() == Node::CDATA_SECTION_NODE)) {
@@ -584,24 +598,33 @@ void KHTMLSelection::calculateStartAndEnd(ETextElement select)
     }
 #endif  // APPLE_CHANGES
 
-	// update the state
+	// adjust the state
 	if (!m_startNode && !m_endNode)
 		m_state = NONE;
-	if (m_startNode == m_endNode && m_startOffset == m_endOffset)
+	else if (m_startNode == m_endNode && m_startOffset == m_endOffset)
 		m_state = CARET;
 	else
 		m_state = RANGE;
+
+    m_needsCaretLayout = true;
     
-    m_startEndValid = true;
+#if EDIT_DEBUG
+    debugPosition();
+#endif
 }
 
-DOMPosition KHTMLSelection::previousCharacterPosition()
+DOMPosition KHTMLSelection::previousCharacterPosition() const
 {
-    if (!startNode())
-        return DOMPosition();
+    return previousCharacterPosition(DOMPosition(startNode(), startOffset()));
+}
 
-	NodeImpl *node = startNode();
-	long offset = startOffset() - 1;
+DOMPosition KHTMLSelection::previousCharacterPosition(const DOMPosition &from)
+{
+    if (!from.node())
+        return from;
+
+	NodeImpl *node = from.node();
+	long offset = from.offset() - 1;
 
     //
     // Look in this renderer
@@ -653,7 +676,7 @@ DOMPosition KHTMLSelection::previousCharacterPosition()
     			 	continue;
     		}
             offset = renderer->caretMaxOffset();
-            if (!renderer->precedesLineBreak())
+            if (renderer->nextEditable() && !renderer->precedesLineBreak())
                 offset--;
             assert(offset >= 0);
             return DOMPosition(renderer->element(), offset);
@@ -662,16 +685,22 @@ DOMPosition KHTMLSelection::previousCharacterPosition()
     }
 
     // can't move the position
-    return DOMPosition(startNode(), startOffset());
+    return from;
+}
+
+
+DOMPosition KHTMLSelection::nextCharacterPosition() const
+{
+    return nextCharacterPosition(DOMPosition(endNode(), endOffset()));
 }
 
-DOMPosition KHTMLSelection::nextCharacterPosition()
+DOMPosition KHTMLSelection::nextCharacterPosition(const DOMPosition &from)
 {
-    if (!endNode())
+    if (!from.node())
         return DOMPosition();
 
-	NodeImpl *node = endNode();
-	long offset = endOffset() + 1;
+ 	NodeImpl *node = from.node();
+ 	long offset = from.offset() + 1;
 
     //
     // Look in this renderer
@@ -746,9 +775,70 @@ DOMPosition KHTMLSelection::nextCharacterPosition()
     }
 
     // can't move the position
-    return DOMPosition(endNode(), endOffset());
+    return from;
 }
 
+bool KHTMLSelection::moveToRenderedContent()
+{
+    if (isEmpty())
+        return false;
+        
+    if (m_state != CARET)
+        return false;
+
+    DOMPosition pos = DOMPosition(startNode(), startOffset());
+    if (inRenderedContent(pos))
+        return true;
+        
+    // not currently rendered, try moving to next
+    DOMPosition next = nextCharacterPosition(pos);
+    if (next != pos) {
+        moveTo(next);
+        return true;
+    }
+
+    // could not be moved to next, try prev
+    DOMPosition prev = previousCharacterPosition(pos);
+    if (prev != pos) {
+        moveTo(prev);
+        return true;
+    }
+    
+    return false;
+}
+
+bool KHTMLSelection::inRenderedContent(const DOMPosition &pos)
+{
+    if (pos.isEmpty())
+        return false;
+        
+ 	long offset = pos.offset();
+
+    RenderObject *renderer = pos.node()->renderer();
+    if (!renderer)
+        return false;
+    
+    if (renderer->isText() && !renderer->isBR()) {
+        RenderText *textRenderer = static_cast<khtml::RenderText *>(renderer);
+        for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+            if (offset >= box->m_start && offset <= box->m_start + box->m_len) {
+                return true;
+            }
+            else if (offset < box->m_start) {
+                // The offset we're looking for is before this node
+                // this means the offset must be in content that is
+                // not rendered. Return false.
+                return false;
+            }
+        }
+    }
+    else if (offset >= renderer->caretMinOffset() && offset <= renderer->caretMaxOffset()) {
+        return true;
+    }
+    
+    return false;
+}
+ 
 bool KHTMLSelection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2) 
 {
 	if (!n1 || !n2) 
@@ -787,7 +877,7 @@ bool KHTMLSelection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2)
         n2 = n2->parentNode();
     }
     // Iterate through the parent's children until n1 or n2 is found
-    n = n1->parentNode()->firstChild();
+    n = n1->parentNode() ? n1->parentNode()->firstChild() : n1->firstChild();
     while (n) {
         if (n == n1) {
             result = true;
@@ -946,7 +1036,7 @@ static bool startAndEndLineNodesIncludingNode(DOM::NodeImpl *node, int offset, K
         if (!lastRunAt (renderNode, selectionPointY, endNode, endOffset))
             return false;
         
-        selection.setSelection(startNode, startOffset, endNode, endOffset);
+        selection.moveTo(startNode, startOffset, endNode, endOffset);
         
         return true;
     }
@@ -1010,7 +1100,7 @@ void KHTMLSelection::debugRenderer(RenderObject *r, bool selected) const
             
             show = show.replace("\n", " ");
             show = show.replace("\r", " ");
-            fprintf(stderr, "==> #text : %s at %d\n", show.latin1(), pos);
+            fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.latin1(), pos);
             fprintf(stderr, "           ");
             for (int i = 0; i < caret; i++)
                 fprintf(stderr, " ");
@@ -1021,7 +1111,7 @@ void KHTMLSelection::debugRenderer(RenderObject *r, bool selected) const
                 text = text.left(max - 3) + "...";
             else
                 text = text.left(max);
-            fprintf(stderr, "    #text : %s\n", text.latin1());
+            fprintf(stderr, "    #text : \"%s\"\n", text.latin1());
         }
     }
 }
diff --git a/WebCore/khtml/xml/dom_selection.h b/WebCore/khtml/xml/dom_selection.h
index e888924..6932b21 100644
--- a/WebCore/khtml/xml/dom_selection.h
+++ b/WebCore/khtml/xml/dom_selection.h
@@ -26,14 +26,9 @@
 #ifndef __khtml_selection_h__
 #define __khtml_selection_h__
 
-#include <qobject.h>
-
 class KHTMLPart;
-class KHTMLPartPrivate;
-class KHTMLView;
 class QPainter;
 class QRect;
-class QTimerEvent;
 
 namespace DOM {
     class DOMPosition;
@@ -45,12 +40,13 @@ namespace khtml {
     class RenderObject;
 }
 
-class KHTMLSelection : public QObject
+class KHTMLSelection
 {
-  Q_OBJECT
-
 public:
     KHTMLSelection();
+    KHTMLSelection(DOM::NodeImpl *node, long offset);
+    KHTMLSelection(const DOM::DOMPosition &);
+    KHTMLSelection(DOM::NodeImpl *startNode, long startOffset, DOM::NodeImpl *endNode, long endOffset);
     KHTMLSelection(const KHTMLSelection &);
     ~KHTMLSelection();
 
@@ -61,41 +57,45 @@ public:
 
 	EState state() const { return m_state; }
 
-    void setSelection(DOM::NodeImpl *node, long offset);
-    void setSelection(const DOM::Range &);
-    void setSelection(const DOM::DOMPosition &);
-    void setSelection(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset);
+    void moveTo(DOM::NodeImpl *node, long offset);
+    void moveTo(const DOM::Range &);
+    void moveTo(const DOM::DOMPosition &);
+    void moveTo(const KHTMLSelection &);
+    void moveTo(DOM::NodeImpl *baseNode, long baseOffset, DOM::NodeImpl *extentNode, long extentOffset);
+    bool modify(EAlter, EDirection, ETextElement);
+    void expandToElement(ETextElement);
+    void clear();
+
+    bool moveToRenderedContent();
+    
     void setBase(DOM::NodeImpl *node, long offset);
     void setExtent(DOM::NodeImpl *node, long offset);
-    void expandSelection(ETextElement);
-    bool alterSelection(EAlter, EDirection, ETextElement);
-    void clearSelection();
-    
+
     DOM::NodeImpl *baseNode() const { return m_baseNode; }
     long baseOffset() const { return m_baseOffset; }
 
     DOM::NodeImpl *extentNode() const { return m_extentNode; }
     long extentOffset() const { return m_extentOffset; }
 
-    DOM::NodeImpl *startNode() const;
-    long startOffset() const;
+    DOM::NodeImpl *startNode() const { return m_startNode; }
+    long startOffset() const { return m_startOffset; }
 
-    DOM::NodeImpl *endNode() const;
-    long endOffset() const;
+    DOM::NodeImpl *endNode() const { return m_endNode; }
+    long endOffset() const { return m_endOffset; }
 
-    void setVisible(bool flag=true);
-    bool visible() const { return m_visible; }
-
-    DOM::DOMPosition previousCharacterPosition();
-    DOM::DOMPosition nextCharacterPosition();
+    DOM::DOMPosition previousCharacterPosition() const;
+    static DOM::DOMPosition previousCharacterPosition(const DOM::DOMPosition &from);
+    DOM::DOMPosition nextCharacterPosition() const;
+    static DOM::DOMPosition nextCharacterPosition(const DOM::DOMPosition &from);
         
-    void invalidate();
+    void setNeedsLayout(bool flag=true);
     
     bool isEmpty() const;
+    DOM::Range toRange() const;
+
     
-#ifdef APPLE_CHANGES
-    void paint(QPainter *p, const QRect &rect) const;
-#endif
+    void debugPosition() const;
+    void debugRenderer(khtml::RenderObject *r, bool selected) const;
 
     KHTMLSelection &operator=(const KHTMLSelection &o);
     
@@ -104,35 +104,30 @@ public:
     
     friend class KHTMLPart;
 
-    void debugPosition() const;
-    void debugRenderer(khtml::RenderObject *r, bool selected) const;
-
 private:
-    void setPart(KHTMLPart *part);
-
-    void update();
+    void init();
+    void validate(ETextElement expandTo=CHARACTER);
 
-    void timerEvent(QTimerEvent *e);
-    void repaint(bool immediate=false) const;
+    void layoutCaret();
+    void needsCaretRepaint();
+    QRect getRepaintRect();
+    void paintCaret(QPainter *p, const QRect &rect);
 
 	void setBaseNode(DOM::NodeImpl *);
 	void setBaseOffset(long);
 	void setExtentNode(DOM::NodeImpl *);
 	void setExtentOffset(long);
 
-	void setStart(DOM::NodeImpl *, long);
 	void setStartNode(DOM::NodeImpl *);
 	void setStartOffset(long);
-	void setEnd(DOM::NodeImpl *, long);
 	void setEndNode(DOM::NodeImpl *);
 	void setEndOffset(long);
 
+    bool inRenderedContent(const DOM::DOMPosition &);
     bool nodeIsBeforeNode(DOM::NodeImpl *n1, DOM::NodeImpl *n2);
 
     void calculateStartAndEnd(ETextElement select=CHARACTER);
     
-    KHTMLPart *m_part;            // part for this selection
-
     DOM::NodeImpl *m_baseNode;    // base node for the selection
     long m_baseOffset;            // offset into base node where selection is
     DOM::NodeImpl *m_extentNode;  // extent node for the selection
@@ -145,24 +140,19 @@ private:
 
 	EState m_state;               // the state of the selection
 
-    int m_caretBlinkTimer;        // caret blink frequency timer id
-	
 	int m_caretX;
 	int m_caretY;
 	int m_caretSize;
 
-	bool m_baseIsStart : 1;     // true if base node is before the extent node
-    bool m_caretBlinks : 1;     // true if caret blinks
-    bool m_caretPaint : 1;      // flag used to deal with blinking the caret
-    bool m_visible : 1;         // true if selection is to be displayed at all
-	bool m_startEndValid : 1;   // true if the start and end are valid
+	bool m_baseIsStart : 1;       // true if base node is before the extent node
+	bool m_needsCaretLayout : 1;  // true if the caret position needs to be calculated
 };
 
 
 inline bool operator==(const KHTMLSelection &a, const KHTMLSelection &b)
 {
-    return a.baseNode() == b.baseNode() && a.baseOffset() == b.baseOffset() &&
-        a.extentNode() == b.extentNode() && a.extentOffset() == b.extentOffset();
+    return a.startNode() == b.startNode() && a.startOffset() == b.startOffset() &&
+        a.endNode() == b.endNode() && a.endOffset() == b.endOffset();
 }
 
 inline bool operator!=(const KHTMLSelection &a, const KHTMLSelection &b)
diff --git a/WebCore/kwq/KWQKHTMLPart.h b/WebCore/kwq/KWQKHTMLPart.h
index a38a3c2..f143620 100644
--- a/WebCore/kwq/KWQKHTMLPart.h
+++ b/WebCore/kwq/KWQKHTMLPart.h
@@ -257,6 +257,8 @@ public:
     void addPluginRootObject(const KJS::Bindings::RootObject *root);
     void cleanupPluginRootObjects();
     
+    void registerCommandForUndo(int cookie);
+    
 private:
     virtual void khtmlMousePressEvent(khtml::MousePressEvent *);
     virtual void khtmlMouseDoubleClickEvent(khtml::MouseDoubleClickEvent *);
diff --git a/WebCore/kwq/KWQKHTMLPart.mm b/WebCore/kwq/KWQKHTMLPart.mm
index 3238ed3..b315e9d 100644
--- a/WebCore/kwq/KWQKHTMLPart.mm
+++ b/WebCore/kwq/KWQKHTMLPart.mm
@@ -2714,7 +2714,7 @@ void KWQKHTMLPart::setShowsFirstResponder(bool flag)
             if (node && node->renderer())
                 node->renderer()->repaint();
         }
-        getKHTMLSelection().setVisible(flag);
+        setSelectionVisible(flag);
     }
 }
 
@@ -2825,3 +2825,7 @@ void KWQKHTMLPart::cleanupPluginRootObjects()
     }
 }
 
+void KWQKHTMLPart::registerCommandForUndo(int cookie)
+{
+    [_bridge registerCommandForUndo:cookie];
+}
diff --git a/WebCore/kwq/WebCoreBridge.h b/WebCore/kwq/WebCoreBridge.h
index 86c67c4..786ec87 100644
--- a/WebCore/kwq/WebCoreBridge.h
+++ b/WebCore/kwq/WebCoreBridge.h
@@ -248,6 +248,8 @@ typedef enum {
 
 - (NSString *)reconstructedSource;
 
+- (void)undoRedoEditing:(id)object;
+
 @end
 
 // The WebCoreBridge protocol contains methods for use by the WebCore side of the bridge.
@@ -384,6 +386,8 @@ typedef enum {
 
 - (jobject)pollForAppletInView:(NSView *)view;
 
+- (void)registerCommandForUndo:(int)cookie;
+
 @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 03e732c..5accdcc 100644
--- a/WebCore/kwq/WebCoreBridge.mm
+++ b/WebCore/kwq/WebCoreBridge.mm
@@ -35,6 +35,7 @@
 #import "htmlattrs.h"
 #import "htmltags.h"
 #import "khtml_part.h"
+#import "khtml_selection.h"
 #import "khtmlview.h"
 #import "kjs_proxy.h"
 #import "kjs_window.h"
@@ -360,7 +361,7 @@ static bool initializedKJS = FALSE;
 
 - (BOOL)isSelectionEditable
 {
-	NodeImpl *startNode = _part->getKHTMLSelection().startNode();
+	NodeImpl *startNode = _part->selection().startNode();
 	return startNode ? startNode->isContentEditable() : NO;
 }
 
@@ -387,9 +388,8 @@ static bool initializedKJS = FALSE;
     node->renderer()->absolutePosition(absX, absY);
     node->renderer()->checkSelectionPoint((int)point.x, (int)point.y, absX, absY, tempNode, offset);
     
-    KHTMLSelection &selection = _part->getKHTMLSelection();
-    selection.setSelection(node, offset);
-    _part->xmlDocImpl()->setSelection(selection);
+    KHTMLSelection selection(node, offset);
+    _part->setSelection(selection);
     
     return YES;
 }
@@ -412,12 +412,12 @@ static bool initializedKJS = FALSE;
 
 - (BOOL)haveSelection
 {
-	return _part->getKHTMLSelection().state() == KHTMLSelection::RANGE;
+	return _part->selection().state() == KHTMLSelection::RANGE;
 }
 
 - (NSString *)selectedHTML
 {
-	return _part->selection().toHTML().string().getNSString();
+	return _part->selection().toRange().toHTML().string().getNSString();
 }
 
 - (NSString *)selectedString
@@ -992,7 +992,8 @@ static HTMLFormElementImpl *formElementFromDOMElement(id <WebDOMElement>element)
 {
     WebCoreDOMNode *startNode = start;
     WebCoreDOMNode *endNode = end;
-    _part->xmlDocImpl()->setSelection([startNode impl], startOffset, [endNode impl], endOffset);
+    KHTMLSelection selection([startNode impl], startOffset, [endNode impl], endOffset);
+    _part->setSelection(selection);
 }
 
 - (NSAttributedString *)selectedAttributedString
@@ -1206,4 +1207,10 @@ static HTMLFormElementImpl *formElementFromDOMElement(id <WebDOMElement>element)
     return string;
 }
 
+- (void)undoRedoEditing:(id)object
+{
+    NSNumber *number = (NSNumber *)object;
+    _part->undoRedoEditing([number intValue]);
+}
+
 @end
diff --git a/WebKit/ChangeLog b/WebKit/ChangeLog
index 174db2e..2488809 100644
--- a/WebKit/ChangeLog
+++ b/WebKit/ChangeLog
@@ -1,3 +1,12 @@
+2004-02-05  Ken Kocienda  <kocienda at apple.com>
+
+        Reviewed by Hyatt
+
+		Added so that editing can hook into Cocoa undo architecture. 
+
+        * WebCoreSupport.subproj/WebBridge.m:
+        (-[WebBridge registerCommandForUndo:]):
+
 2004-02-04  David Hyatt  <hyatt at apple.com>
 
 	Fix deployment build bustage.
@@ -9,6 +18,7 @@
 
 	Fixed: <rdar://problem/3546028>: Safari should not give plug-ins any time, thus use 0% CPU, when not in the currently active session
 
+
         Reviewed by john.
 
         * Plugins.subproj/WebBaseNetscapePluginView.m:
diff --git a/WebKit/WebCoreSupport.subproj/WebBridge.m b/WebKit/WebCoreSupport.subproj/WebBridge.m
index 656fcc5..74ab667 100644
--- a/WebKit/WebCoreSupport.subproj/WebBridge.m
+++ b/WebKit/WebCoreSupport.subproj/WebBridge.m
@@ -1161,5 +1161,10 @@ static id <WebFormDelegate> formDelegate(WebBridge *self)
     return applet;
 }
 
+- (void)registerCommandForUndo:(int)cookie
+{
+    NSUndoManager *undoManager = [[_frame webView] undoManager];
+    [undoManager registerUndoWithTarget:self selector:@selector(undoRedoEditing:) object:[NSNumber numberWithInt:cookie]];
+}
 
 @end

-- 
WebKit Debian packaging



More information about the Pkg-webkit-commits mailing list