Reviewed by Dave.
authormjs <mjs@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 13 May 2005 08:37:15 +0000 (08:37 +0000)
committermjs <mjs@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 13 May 2005 08:37:15 +0000 (08:37 +0000)
- more splitting up of htmlediting.cpp

        * WebCore.pbproj/project.pbxproj:
        * khtml/editing/composite_edit_command.cpp:
        * khtml/editing/delete_from_text_node_command.cpp: Added.
        * khtml/editing/delete_from_text_node_command.h: Added.
        * khtml/editing/delete_selection_command.cpp: Added.
        * khtml/editing/delete_selection_command.h: Added.
        * khtml/editing/htmlediting.cpp:
        * khtml/editing/htmlediting.h:
        * khtml/editing/insert_into_text_node_command.cpp: Added.
        * khtml/editing/insert_into_text_node_command.h: Added.
        * khtml/editing/insert_node_before_command.cpp: Added.
        * khtml/editing/insert_node_before_command.h: Added.

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

13 files changed:
WebCore/ChangeLog-2005-08-23
WebCore/WebCore.pbproj/project.pbxproj
WebCore/khtml/editing/composite_edit_command.cpp
WebCore/khtml/editing/delete_from_text_node_command.cpp [new file with mode: 0644]
WebCore/khtml/editing/delete_from_text_node_command.h [new file with mode: 0644]
WebCore/khtml/editing/delete_selection_command.cpp [new file with mode: 0644]
WebCore/khtml/editing/delete_selection_command.h [new file with mode: 0644]
WebCore/khtml/editing/htmlediting.cpp
WebCore/khtml/editing/htmlediting.h
WebCore/khtml/editing/insert_into_text_node_command.cpp [new file with mode: 0644]
WebCore/khtml/editing/insert_into_text_node_command.h [new file with mode: 0644]
WebCore/khtml/editing/insert_node_before_command.cpp [new file with mode: 0644]
WebCore/khtml/editing/insert_node_before_command.h [new file with mode: 0644]

index 74260a8fdeb22053458d581d2d4a17c61ec236ce..8ec43a3aaa959bdff0383a4e79ae280785473afd 100644 (file)
@@ -1,3 +1,22 @@
+2005-05-13  Maciej Stachowiak  <mjs@apple.com>
+
+        Reviewed by Dave.
+
+       - more splitting up of htmlediting.cpp
+
+        * WebCore.pbproj/project.pbxproj:
+        * khtml/editing/composite_edit_command.cpp:
+        * khtml/editing/delete_from_text_node_command.cpp: Added.
+        * khtml/editing/delete_from_text_node_command.h: Added.
+        * khtml/editing/delete_selection_command.cpp: Added.
+        * khtml/editing/delete_selection_command.h: Added.
+        * khtml/editing/htmlediting.cpp:
+        * khtml/editing/htmlediting.h:
+        * khtml/editing/insert_into_text_node_command.cpp: Added.
+        * khtml/editing/insert_into_text_node_command.h: Added.
+        * khtml/editing/insert_node_before_command.cpp: Added.
+        * khtml/editing/insert_node_before_command.h: Added.
+
 2005-05-12  Adele Peterson  <adele@apple.com>
 
         Reviewed by Maciej.
index 219e21fc708bf63137a815e2b9f256a7590711f3..7a225fcde175e7fcb5c7876b88ac8daed056d4b5 100644 (file)
 //652
 //653
 //654
+               652C6E9708331AE400714969 = {
+                       fileEncoding = 30;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.cpp.cpp;
+                       name = delete_from_text_node_command.cpp;
+                       path = editing/delete_from_text_node_command.cpp;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               652C6E9808331AE400714969 = {
+                       fileEncoding = 30;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.h;
+                       name = delete_from_text_node_command.h;
+                       path = editing/delete_from_text_node_command.h;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               652C6E9908331AE400714969 = {
+                       fileEncoding = 30;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.cpp.cpp;
+                       name = delete_selection_command.cpp;
+                       path = editing/delete_selection_command.cpp;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               652C6E9A08331AE400714969 = {
+                       fileEncoding = 30;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.h;
+                       name = delete_selection_command.h;
+                       path = editing/delete_selection_command.h;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               652C6E9B08331AE400714969 = {
+                       fileRef = 652C6E9708331AE400714969;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               652C6E9C08331AE400714969 = {
+                       fileRef = 652C6E9808331AE400714969;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               652C6E9D08331AE400714969 = {
+                       fileRef = 652C6E9908331AE400714969;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               652C6E9E08331AE400714969 = {
+                       fileRef = 652C6E9A08331AE400714969;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               652C6E9F083328A700714969 = {
+                       fileEncoding = 30;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.cpp.cpp;
+                       name = insert_into_text_node_command.cpp;
+                       path = editing/insert_into_text_node_command.cpp;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               652C6EA0083328A700714969 = {
+                       fileEncoding = 30;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.h;
+                       name = insert_into_text_node_command.h;
+                       path = editing/insert_into_text_node_command.h;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               652C6EA1083328A700714969 = {
+                       fileEncoding = 30;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.cpp.cpp;
+                       name = insert_node_before_command.cpp;
+                       path = editing/insert_node_before_command.cpp;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               652C6EA2083328A700714969 = {
+                       fileEncoding = 30;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.h;
+                       name = insert_node_before_command.h;
+                       path = editing/insert_node_before_command.h;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               652C6EA3083328A700714969 = {
+                       fileRef = 652C6E9F083328A700714969;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               652C6EA4083328A700714969 = {
+                       fileRef = 652C6EA0083328A700714969;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               652C6EA5083328A700714969 = {
+                       fileRef = 652C6EA1083328A700714969;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               652C6EA6083328A700714969 = {
+                       fileRef = 652C6EA2083328A700714969;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
                654D87B50831973B0082DCA1 = {
                        fileEncoding = 30;
                        isa = PBXFileReference;
                                65DC16C50831DD6F0022744E,
                                65AC79A20831ED6D009385CE,
                                65AC79AA0831F006009385CE,
+                               652C6E9C08331AE400714969,
+                               652C6E9E08331AE400714969,
+                               652C6EA4083328A700714969,
+                               652C6EA6083328A700714969,
                        );
                        isa = PBXHeadersBuildPhase;
                        runOnlyForDeploymentPostprocessing = 0;
                                65DC16C40831DD6F0022744E,
                                65AC79A10831ED6D009385CE,
                                65AC79A90831F006009385CE,
+                               652C6E9B08331AE400714969,
+                               652C6E9D08331AE400714969,
+                               652C6EA3083328A700714969,
+                               652C6EA5083328A700714969,
                        );
                        isa = PBXSourcesBuildPhase;
                        runOnlyForDeploymentPostprocessing = 0;
                                65AC79A70831F006009385CE,
                                65DC16C30831DD6F0022744E,
                                65DC16C20831DD6F0022744E,
+                               652C6E9808331AE400714969,
+                               652C6E9708331AE400714969,
+                               652C6E9A08331AE400714969,
+                               652C6E9908331AE400714969,
                                EDA4AC97076FB89100DD23EC,
                                654D87B60831973B0082DCA1,
                                654D87B50831973B0082DCA1,
                                BE9185DD05EE59B80081354D,
                                BE9185E005EE59B80081354D,
-                               BEA5DBDA075CEDA00098A432,
                                BEA5E01D075CEDAC0098A432,
+                               BEA5DBDA075CEDA00098A432,
+                               652C6EA0083328A700714969,
+                               652C6E9F083328A700714969,
+                               652C6EA2083328A700714969,
+                               652C6EA1083328A700714969,
                                BE02D4E6066F908A0076809F,
                                BE02D4E7066F908A0076809F,
                                9378D9FA07640A46004B97BF,
index 28f74f8eac563b14949afe5d57ebde335d0fc2f9..1a6238836a9eb7fcecd3080e651fff3cb3a72b6b 100644 (file)
@@ -26,6 +26,9 @@
 #include "composite_edit_command.h"
 
 #include "append_node_command.h"
+#include "delete_from_text_node_command.h"
+#include "insert_into_text_node_command.h"
+#include "insert_node_before_command.h"
 #include "htmlediting.h"
 #include "visible_units.h"
 
@@ -245,7 +248,7 @@ void CompositeEditCommand::inputText(const DOMString &text, bool selectInsertedT
 
 void CompositeEditCommand::insertTextIntoNode(TextImpl *node, long offset, const DOMString &text)
 {
-    EditCommandPtr cmd(new InsertIntoTextNode(document(), node, offset, text));
+    EditCommandPtr cmd(new InsertIntoTextNodeCommand(document(), node, offset, text));
     applyCommandToComposite(cmd);
 }
 
@@ -259,7 +262,7 @@ void CompositeEditCommand::replaceTextInNode(TextImpl *node, long offset, long c
 {
     EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count));
     applyCommandToComposite(deleteCommand);
-    EditCommandPtr insertCommand(new InsertIntoTextNode(document(), node, offset, replacementText));
+    EditCommandPtr insertCommand(new InsertIntoTextNodeCommand(document(), node, offset, replacementText));
     applyCommandToComposite(insertCommand);
 }
 
diff --git a/WebCore/khtml/editing/delete_from_text_node_command.cpp b/WebCore/khtml/editing/delete_from_text_node_command.cpp
new file mode 100644 (file)
index 0000000..c4ad25e
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "delete_from_text_node_command.h"
+
+#include "xml/dom_textimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::TextImpl;
+
+namespace khtml {
+
+DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(DocumentImpl *document, TextImpl *node, long offset, long count)
+    : EditCommand(document), m_node(node), m_offset(offset), m_count(count)
+{
+    ASSERT(m_node);
+    ASSERT(m_offset >= 0);
+    ASSERT(m_offset < (long)m_node->length());
+    ASSERT(m_count >= 0);
+    
+    m_node->ref();
+}
+
+DeleteFromTextNodeCommand::~DeleteFromTextNodeCommand()
+{
+    ASSERT(m_node);
+    m_node->deref();
+}
+
+void DeleteFromTextNodeCommand::doApply()
+{
+    ASSERT(m_node);
+
+    int exceptionCode = 0;
+    m_text = m_node->substringData(m_offset, m_count, exceptionCode);
+    ASSERT(exceptionCode == 0);
+    
+    m_node->deleteData(m_offset, m_count, exceptionCode);
+    ASSERT(exceptionCode == 0);
+}
+
+void DeleteFromTextNodeCommand::doUnapply()
+{
+    ASSERT(m_node);
+    ASSERT(!m_text.isEmpty());
+
+    int exceptionCode = 0;
+    m_node->insertData(m_offset, m_text, exceptionCode);
+    ASSERT(exceptionCode == 0);
+}
+
+} // namespace khtml
diff --git a/WebCore/khtml/editing/delete_from_text_node_command.h b/WebCore/khtml/editing/delete_from_text_node_command.h
new file mode 100644 (file)
index 0000000..6827d5d
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#ifndef __delete_from_text_node_command_h__
+#define __delete_from_text_node_command_h__
+
+#include "edit_command.h"
+
+#include "dom/dom_string.h"
+
+namespace DOM {
+    class TextImpl;
+}
+
+namespace khtml {
+
+class DeleteFromTextNodeCommand : public EditCommand
+{
+public:
+    DeleteFromTextNodeCommand(DOM::DocumentImpl *document, DOM::TextImpl *node, long offset, long count);
+    virtual ~DeleteFromTextNodeCommand();
+       
+    virtual void doApply();
+    virtual void doUnapply();
+
+    DOM::TextImpl *node() const { return m_node; }
+    long offset() const { return m_offset; }
+    long count() const { return m_count; }
+
+private:
+    DOM::TextImpl *m_node;
+    long m_offset;
+    long m_count;
+    DOM::DOMString m_text;
+};
+
+} // namespace khtml
+
+#endif // __delete_from_text_node_command_h__
diff --git a/WebCore/khtml/editing/delete_selection_command.cpp b/WebCore/khtml/editing/delete_selection_command.cpp
new file mode 100644 (file)
index 0000000..53bcf7c
--- /dev/null
@@ -0,0 +1,768 @@
+/*
+ * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "delete_selection_command.h"
+
+#include "css/css_computedstyle.h"
+#include "htmlediting.h"
+#include "khtml_part.h"
+#include "misc/htmltags.h"
+#include "rendering/render_line.h"
+#include "rendering/render_object.h"
+#include "visible_text.h"
+#include "visible_units.h"
+#include "xml/dom2_rangeimpl.h"
+#include "xml/dom_position.h"
+#include "xml/dom_textimpl.h"
+
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#include "KWQLogging.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#define LOG(channel, formatAndArgs...) ((void)0)
+#endif
+
+using DOM::CSSComputedStyleDeclarationImpl;
+using DOM::DOMString;
+using DOM::DocumentImpl;
+using DOM::NodeImpl;
+using DOM::Position;
+using DOM::RangeImpl;
+using DOM::TextImpl;
+
+namespace khtml {
+
+static bool isListStructureNode(const NodeImpl *node)
+{
+    // FIXME: Irritating that we can get away with just going at the render tree for isTableStructureNode,
+    // but here we also have to peek at the type of DOM node?
+    RenderObject *r = node->renderer();
+    NodeImpl::Id nodeID = node->id();
+    return (r && r->isListItem())
+        || (nodeID == ID_OL || nodeID == ID_UL || nodeID == ID_DD || nodeID == ID_DT || nodeID == ID_DIR || nodeID == ID_MENU);
+}
+
+static int maxDeepOffset(NodeImpl *n)
+{
+    if (n->isAtomicNode())
+        return n->caretMaxOffset();
+
+    if (n->isElementNode())
+        return n->childNodeCount();
+
+    return 1;
+}
+
+static void debugPosition(const char *prefix, const Position &pos)
+{
+    if (!prefix)
+        prefix = "";
+    if (pos.isNull())
+        LOG(Editing, "%s <null>", prefix);
+    else
+        LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
+}
+
+static void debugNode(const char *prefix, const NodeImpl *node)
+{
+    if (!prefix)
+        prefix = "";
+    if (!node)
+        LOG(Editing, "%s <null>", prefix);
+    else
+        LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node);
+}
+
+static Position positionBeforePossibleContainingSpecialElement(const Position &pos)
+{
+    if (isFirstVisiblePositionInSpecialElement(pos)) {
+        return positionBeforeContainingSpecialElement(pos);
+    } 
+
+    return pos;
+}
+
+static Position positionAfterPossibleContainingSpecialElement(const Position &pos)
+{
+    if (isLastVisiblePositionInSpecialElement(pos)) {
+        return positionAfterContainingSpecialElement(pos);
+    }
+
+    return pos;
+}
+
+DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, bool smartDelete, bool mergeBlocksAfterDelete)
+    : CompositeEditCommand(document), 
+      m_hasSelectionToDelete(false), 
+      m_smartDelete(smartDelete), 
+      m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
+      m_startBlock(0),
+      m_endBlock(0),
+      m_startNode(0),
+      m_typingStyle(0)
+{
+}
+
+DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
+    : CompositeEditCommand(document), 
+      m_hasSelectionToDelete(true), 
+      m_smartDelete(smartDelete), 
+      m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
+      m_selectionToDelete(selection),
+      m_startBlock(0),
+      m_endBlock(0),
+      m_startNode(0),
+      m_typingStyle(0)
+{
+}
+
+void DeleteSelectionCommand::initializePositionData()
+{
+    //
+    // Handle setting some basic positions
+    //
+    Position start = m_selectionToDelete.start();
+    start = positionOutsideContainingSpecialElement(start);
+    Position end = m_selectionToDelete.end();
+    end = positionOutsideContainingSpecialElement(end);
+
+    m_upstreamStart = positionBeforePossibleContainingSpecialElement(start.upstream());
+    m_downstreamStart = positionBeforePossibleContainingSpecialElement(start.downstream());
+    m_upstreamEnd = positionAfterPossibleContainingSpecialElement(end.upstream());
+    m_downstreamEnd = positionAfterPossibleContainingSpecialElement(end.downstream());
+
+    //
+    // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
+    //
+    m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity());
+    m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
+
+    if (m_smartDelete) {
+    
+        // skip smart delete if the selection to delete already starts or ends with whitespace
+        Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()).deepEquivalent();
+        bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
+        if (!skipSmartDelete)
+            skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
+
+        // extend selection upstream if there is whitespace there
+        bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity(), true).isNotNull();
+        if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
+            VisiblePosition visiblePos = VisiblePosition(start, m_selectionToDelete.startAffinity()).previous();
+            pos = visiblePos.deepEquivalent();
+            // Expand out one character upstream for smart delete and recalculate
+            // positions based on this change.
+            m_upstreamStart = pos.upstream();
+            m_downstreamStart = pos.downstream();
+            m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
+        }
+        
+        // trailing whitespace is only considered for smart delete if there is no leading
+        // whitespace, as in the case where you double-click the first word of a paragraph.
+        if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
+            // Expand out one character downstream for smart delete and recalculate
+            // positions based on this change.
+            pos = VisiblePosition(end, m_selectionToDelete.endAffinity()).next().deepEquivalent();
+            m_upstreamEnd = pos.upstream();
+            m_downstreamEnd = pos.downstream();
+            m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
+        }
+    }
+    
+    m_trailingWhitespaceValid = true;
+    
+    //
+    // Handle setting start and end blocks and the start node.
+    //
+    m_startBlock = m_downstreamStart.node()->enclosingBlockFlowElement();
+    m_startBlock->ref();
+    m_endBlock = m_upstreamEnd.node()->enclosingBlockFlowElement();
+    m_endBlock->ref();
+    m_startNode = m_upstreamStart.node();
+    m_startNode->ref();
+
+    //
+    // Handle detecting if the line containing the selection end is itself fully selected.
+    // This is one of the tests that determines if block merging of content needs to be done.
+    //
+    VisiblePosition visibleEnd(end, m_selectionToDelete.endAffinity());
+    if (isStartOfParagraph(visibleEnd) || isEndOfParagraph(visibleEnd)) {
+        Position previousLineStart = previousLinePosition(visibleEnd, 0).deepEquivalent();
+        if (previousLineStart.isNull() || RangeImpl::compareBoundaryPoints(previousLineStart, m_downstreamStart) >= 0)
+            m_mergeBlocksAfterDelete = false;
+    }
+
+    debugPosition("m_upstreamStart      ", m_upstreamStart);
+    debugPosition("m_downstreamStart    ", m_downstreamStart);
+    debugPosition("m_upstreamEnd        ", m_upstreamEnd);
+    debugPosition("m_downstreamEnd      ", m_downstreamEnd);
+    debugPosition("m_leadingWhitespace  ", m_leadingWhitespace);
+    debugPosition("m_trailingWhitespace ", m_trailingWhitespace);
+    debugNode(    "m_startBlock         ", m_startBlock);
+    debugNode(    "m_endBlock           ", m_endBlock);    
+    debugNode(    "m_startNode          ", m_startNode);    
+}
+
+void DeleteSelectionCommand::insertPlaceholderForAncestorBlockContent()
+{
+    // This code makes sure a line does not disappear when deleting in this case:
+    // <p>foo</p>bar<p>baz</p>
+    // Select "bar" and hit delete. If nothing is done, the line containing bar will disappear.
+    // It needs to be held open by inserting a placeholder.
+    // Also see:
+    // <rdar://problem/3928305> selecting an entire line and typing over causes new inserted text at top of document
+    //
+    // The checks below detect the case where the selection contains content in an ancestor block 
+    // surrounded by child blocks.
+    //
+    VisiblePosition visibleStart(m_upstreamStart, VP_DEFAULT_AFFINITY);
+    VisiblePosition beforeStart = visibleStart.previous();
+    NodeImpl *startBlock = enclosingBlockFlowElement(visibleStart);
+    NodeImpl *beforeStartBlock = enclosingBlockFlowElement(beforeStart);
+    
+    if (!beforeStart.isNull() &&
+        !inSameBlock(visibleStart, beforeStart) &&
+        beforeStartBlock->isAncestor(startBlock) &&
+        startBlock != m_upstreamStart.node()) {
+
+        VisiblePosition visibleEnd(m_downstreamEnd, VP_DEFAULT_AFFINITY);
+        VisiblePosition afterEnd = visibleEnd.next();
+        
+        if ((!afterEnd.isNull() && !inSameBlock(afterEnd, visibleEnd) && !inSameBlock(afterEnd, visibleStart)) ||
+            (m_downstreamEnd == m_selectionToDelete.end() && isEndOfParagraph(visibleEnd))) {
+            NodeImpl *block = createDefaultParagraphElement(document());
+            insertNodeBefore(block, m_upstreamStart.node());
+            addBlockPlaceholderIfNeeded(block);
+            m_endingPosition = Position(block, 0);
+        }
+    }
+}
+
+void DeleteSelectionCommand::saveTypingStyleState()
+{
+    // Figure out the typing style in effect before the delete is done.
+    // FIXME: Improve typing style.
+    // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
+    CSSComputedStyleDeclarationImpl *computedStyle = m_selectionToDelete.start().computedStyle();
+    computedStyle->ref();
+    m_typingStyle = computedStyle->copyInheritableProperties();
+    m_typingStyle->ref();
+    computedStyle->deref();
+}
+
+bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
+{
+    // Check for special-case where the selection contains only a BR on a line by itself after another BR.
+    bool upstreamStartIsBR = m_startNode->id() == ID_BR;
+    bool downstreamStartIsBR = m_downstreamStart.node()->id() == ID_BR;
+    bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
+    if (isBROnLineByItself) {
+        removeNode(m_downstreamStart.node());
+        m_endingPosition = m_upstreamStart;
+        m_mergeBlocksAfterDelete = false;
+        return true;
+    }
+
+    // Not a special-case delete per se, but we can detect that the merging of content between blocks
+    // should not be done.
+    if (upstreamStartIsBR && downstreamStartIsBR)
+        m_mergeBlocksAfterDelete = false;
+
+    return false;
+}
+
+void DeleteSelectionCommand::setStartNode(NodeImpl *node)
+{
+    NodeImpl *old = m_startNode;
+    m_startNode = node;
+    if (m_startNode)
+        m_startNode->ref();
+    if (old)
+        old->deref();
+}
+
+void DeleteSelectionCommand::handleGeneralDelete()
+{
+    int startOffset = m_upstreamStart.offset();
+    VisiblePosition visibleEnd = VisiblePosition(m_downstreamEnd, m_selectionToDelete.endAffinity());
+    bool endAtEndOfBlock = isEndOfBlock(visibleEnd);
+
+    // Handle some special cases where the selection begins and ends on specific visible units.
+    // Sometimes a node that is actually selected needs to be retained in order to maintain
+    // user expectations for the delete operation. Here is an example:
+    //     1. Open a new Blot or Mail document
+    //     2. hit Return ten times or so
+    //     3. Type a letter (do not hit Return after it)
+    //     4. Type shift-up-arrow to select the line containing the letter and the previous blank line
+    //     5. Hit Delete
+    // You expect the insertion point to wind up at the start of the line where your selection began.
+    // Because of the nature of HTML, the editing code needs to perform a special check to get
+    // this behavior. So:
+    // If the entire start block is selected, and the selection does not extend to the end of the 
+    // end of a block other than the block containing the selection start, then do not delete the 
+    // start block, otherwise delete the start block.
+    // A similar case is provided to cover selections starting in BR elements.
+    if (startOffset == 1 && m_startNode && m_startNode->id() == ID_BR) {
+        setStartNode(m_startNode->traverseNextNode());
+        startOffset = 0;
+    }
+    if (m_startBlock != m_endBlock && startOffset == 0 && m_startNode && m_startNode->id() == ID_BR && endAtEndOfBlock) {
+        // Don't delete the BR element
+        setStartNode(m_startNode->traverseNextNode());
+    }
+    else if (m_startBlock != m_endBlock && isStartOfBlock(VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()))) {
+        if (!m_startBlock->isAncestor(m_endBlock) && !isStartOfBlock(visibleEnd) && endAtEndOfBlock) {
+            // Delete all the children of the block, but not the block itself.
+            setStartNode(m_startBlock->firstChild());
+            startOffset = 0;
+        }
+    }
+    else if (startOffset >= m_startNode->caretMaxOffset() &&
+             (m_startNode->isAtomicNode() || startOffset == 0)) {
+        // Move the start node to the next node in the tree since the startOffset is equal to
+        // or beyond the start node's caretMaxOffset This means there is nothing visible to delete. 
+        // But don't do this if the node is not atomic - we don't want to move into the first child.
+
+        // Also, before moving on, delete any insignificant text that may be present in a text node.
+        if (m_startNode->isTextNode()) {
+            // Delete any insignificant text from this node.
+            TextImpl *text = static_cast<TextImpl *>(m_startNode);
+            if (text->length() > (unsigned)m_startNode->caretMaxOffset())
+                deleteTextFromNode(text, m_startNode->caretMaxOffset(), text->length() - m_startNode->caretMaxOffset());
+        }
+        
+        // shift the start node to the next
+        setStartNode(m_startNode->traverseNextNode());
+        startOffset = 0;
+    }
+
+    // Done adjusting the start.  See if we're all done.
+    if (!m_startNode)
+        return;
+
+    if (m_startNode == m_downstreamEnd.node()) {
+        // The selection to delete is all in one node.
+        if (!m_startNode->renderer() || 
+            (startOffset == 0 && m_downstreamEnd.offset() >= maxDeepOffset(m_startNode))) {
+            // just delete
+            removeFullySelectedNode(m_startNode);
+        } else if (m_downstreamEnd.offset() - startOffset > 0) {
+            if (m_startNode->isTextNode()) {
+                // in a text node that needs to be trimmed
+                TextImpl *text = static_cast<TextImpl *>(m_startNode);
+                deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset);
+                m_trailingWhitespaceValid = false;
+            } else {
+                removeChildrenInRange(m_startNode, startOffset, m_downstreamEnd.offset());
+                m_endingPosition = m_upstreamStart;
+            }
+        }
+    }
+    else {
+        // The selection to delete spans more than one node.
+        NodeImpl *node = m_startNode;
+        
+        if (startOffset > 0) {
+            if (m_startNode->isTextNode()) {
+                // in a text node that needs to be trimmed
+                TextImpl *text = static_cast<TextImpl *>(node);
+                deleteTextFromNode(text, startOffset, text->length() - startOffset);
+                node = node->traverseNextNode();
+            } else {
+                node = m_startNode->childNode(startOffset);
+            }
+        }
+        
+        // handle deleting all nodes that are completely selected
+        while (node && node != m_downstreamEnd.node()) {
+            if (RangeImpl::compareBoundaryPoints(Position(node, 0), m_downstreamEnd) >= 0) {
+                // traverseNextSibling just blew past the end position, so stop deleting
+                node = 0;
+            } else if (!m_downstreamEnd.node()->isAncestor(node)) {
+                NodeImpl *nextNode = node->traverseNextSibling();
+                // if we just removed a node from the end container, update end position so the
+                // check above will work
+                if (node->parentNode() == m_downstreamEnd.node()) {
+                    ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.offset());
+                    m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.offset() - 1);
+                }
+                removeFullySelectedNode(node);
+                node = nextNode;
+            } else {
+                NodeImpl *n = node->lastChild();
+                while (n && n->lastChild())
+                    n = n->lastChild();
+                if (n == m_downstreamEnd.node() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMaxOffset()) {
+                    removeFullySelectedNode(node);
+                    m_trailingWhitespaceValid = false;
+                    node = 0;
+                } 
+                else {
+                    node = node->traverseNextNode();
+                }
+            }
+        }
+
+        
+        if (m_downstreamEnd.node() != m_startNode && !m_upstreamStart.node()->isAncestor(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMinOffset()) {
+            if (m_downstreamEnd.offset() >= maxDeepOffset(m_downstreamEnd.node())) {
+                // need to delete whole node
+                // we can get here if this is the last node in the block
+                // remove an ancestor of m_downstreamEnd.node(), and thus m_downstreamEnd.node() itself
+                if (!m_upstreamStart.node()->inDocument() ||
+                    m_upstreamStart.node() == m_downstreamEnd.node() ||
+                    m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
+                    m_upstreamStart = Position(m_downstreamEnd.node()->parentNode(), m_downstreamEnd.node()->nodeIndex());
+                }
+                
+                removeFullySelectedNode(m_downstreamEnd.node());
+                m_trailingWhitespaceValid = false;
+            } else {
+                if (m_downstreamEnd.node()->isTextNode()) {
+                    // in a text node that needs to be trimmed
+                    TextImpl *text = static_cast<TextImpl *>(m_downstreamEnd.node());
+                    if (m_downstreamEnd.offset() > 0) {
+                        deleteTextFromNode(text, 0, m_downstreamEnd.offset());
+                        m_downstreamEnd = Position(text, 0);
+                        m_trailingWhitespaceValid = false;
+                    }
+                } else {
+                    int offset = 0;
+                    if (m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
+                        NodeImpl *n = m_upstreamStart.node();
+                        while (n && n->parentNode() != m_downstreamEnd.node())
+                            n = n->parentNode();
+                        if (n)
+                            offset = n->nodeIndex() + 1;
+                    }
+                    removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.offset());
+                    m_downstreamEnd = Position(m_downstreamEnd.node(), offset);
+                }
+            }
+        }
+    }
+}
+
+static DOMString &nonBreakingSpaceString()
+{
+    static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
+    return nonBreakingSpaceString;
+}
+
+// FIXME: Can't really determine this without taking white-space mode into account.
+static inline bool nextCharacterIsCollapsibleWhitespace(const Position &pos)
+{
+    if (!pos.node())
+        return false;
+    if (!pos.node()->isTextNode())
+        return false;
+    return isCollapsibleWhitespace(static_cast<TextImpl *>(pos.node())->data()[pos.offset()]);
+}
+
+void DeleteSelectionCommand::fixupWhitespace()
+{
+    document()->updateLayout();
+    if (m_leadingWhitespace.isNotNull() && (m_trailingWhitespace.isNotNull() || !m_leadingWhitespace.isRenderedCharacter())) {
+        LOG(Editing, "replace leading");
+        TextImpl *textNode = static_cast<TextImpl *>(m_leadingWhitespace.node());
+        replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
+    }
+    else if (m_trailingWhitespace.isNotNull()) {
+        if (m_trailingWhitespaceValid) {
+            if (!m_trailingWhitespace.isRenderedCharacter()) {
+                LOG(Editing, "replace trailing [valid]");
+                TextImpl *textNode = static_cast<TextImpl *>(m_trailingWhitespace.node());
+                replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
+            }
+        }
+        else {
+            Position pos = m_endingPosition.downstream();
+            pos = Position(pos.node(), pos.offset() - 1);
+            if (nextCharacterIsCollapsibleWhitespace(pos) && !pos.isRenderedCharacter()) {
+                LOG(Editing, "replace trailing [invalid]");
+                TextImpl *textNode = static_cast<TextImpl *>(pos.node());
+                replaceTextInNode(textNode, pos.offset(), 1, nonBreakingSpaceString());
+                // need to adjust ending position since the trailing position is not valid.
+                m_endingPosition = pos;
+            }
+        }
+    }
+}
+
+// This function moves nodes in the block containing startNode to dstBlock, starting
+// from startNode and proceeding to the end of the paragraph. Nodes in the block containing
+// startNode that appear in document order before startNode are not moved.
+// This function is an important helper for deleting selections that cross paragraph
+// boundaries.
+void DeleteSelectionCommand::moveNodesAfterNode()
+{
+    if (!m_mergeBlocksAfterDelete)
+        return;
+
+    if (m_endBlock == m_startBlock)
+        return;
+
+    NodeImpl *startNode = m_downstreamEnd.node();
+    NodeImpl *dstNode = m_upstreamStart.node();
+
+    if (!startNode->inDocument() || !dstNode->inDocument())
+        return;
+
+    NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
+    if (isTableStructureNode(startBlock) || isListStructureNode(startBlock))
+        // Do not move content between parts of a table or list.
+        return;
+
+    // Now that we are about to add content, check to see if a placeholder element
+    // can be removed.
+    removeBlockPlaceholder(startBlock);
+
+    // Move the subtree containing node
+    NodeImpl *node = startNode->enclosingInlineElement();
+
+    // Insert after the subtree containing destNode
+    NodeImpl *refNode = dstNode->enclosingInlineElement();
+
+    // Nothing to do if start is already at the beginning of dstBlock
+    NodeImpl *dstBlock = refNode->enclosingBlockFlowElement();
+    if (startBlock == dstBlock->firstChild())
+        return;
+
+    // Do the move.
+    NodeImpl *rootNode = refNode->rootEditableElement();
+    while (node && node->isAncestor(startBlock)) {
+        NodeImpl *moveNode = node;
+        node = node->nextSibling();
+        removeNode(moveNode);
+        if (moveNode->id() == ID_BR && !moveNode->renderer()) {
+            // Just remove this node, and don't put it back.
+            // If the BR was not rendered (since it was at the end of a block, for instance), 
+            // putting it back in the document might make it appear, and that is not desirable.
+            break;
+        }
+        if (refNode == rootNode)
+            insertNodeAt(moveNode, refNode, 0);
+        else
+            insertNodeAfter(moveNode, refNode);
+        refNode = moveNode;
+        if (moveNode->id() == ID_BR)
+            break;
+    }
+
+    // If the startBlock no longer has any kids, we may need to deal with adding a BR
+    // to make the layout come out right. Consider this document:
+    //
+    // One
+    // <div>Two</div>
+    // Three
+    // 
+    // Placing the insertion before before the 'T' of 'Two' and hitting delete will
+    // move the contents of the div to the block containing 'One' and delete the div.
+    // This will have the side effect of moving 'Three' on to the same line as 'One'
+    // and 'Two'. This is undesirable. We fix this up by adding a BR before the 'Three'.
+    // This may not be ideal, but it is better than nothing.
+    document()->updateLayout();
+    if (!startBlock->renderer() || !startBlock->renderer()->firstChild()) {
+        removeNode(startBlock);
+        document()->updateLayout();
+        if (refNode->renderer() && refNode->renderer()->inlineBox() && refNode->renderer()->inlineBox()->nextOnLineExists()) {
+            insertNodeAfter(createBreakElement(document()), refNode);
+        }
+    }
+}
+
+void DeleteSelectionCommand::calculateEndingPosition()
+{
+    if (m_endingPosition.isNotNull() && m_endingPosition.node()->inDocument())
+        return;
+
+    m_endingPosition = m_upstreamStart;
+    if (m_endingPosition.node()->inDocument())
+        return;
+    
+    m_endingPosition = m_downstreamEnd;
+    if (m_endingPosition.node()->inDocument())
+        return;
+
+    m_endingPosition = Position(m_startBlock, 0);
+    if (m_endingPosition.node()->inDocument())
+        return;
+
+    m_endingPosition = Position(m_endBlock, 0);
+    if (m_endingPosition.node()->inDocument())
+        return;
+
+    m_endingPosition = Position(document()->documentElement(), 0);
+}
+
+void DeleteSelectionCommand::calculateTypingStyleAfterDelete(NodeImpl *insertedPlaceholder)
+{
+    // Compute the difference between the style before the delete and the style now
+    // after the delete has been done. Set this style on the part, so other editing
+    // commands being composed with this one will work, and also cache it on the command,
+    // so the KHTMLPart::appliedEditing can set it after the whole composite command 
+    // has completed.
+    // FIXME: Improve typing style.
+    // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
+    CSSComputedStyleDeclarationImpl endingStyle(m_endingPosition.node());
+    endingStyle.diff(m_typingStyle);
+    if (!m_typingStyle->length()) {
+        m_typingStyle->deref();
+        m_typingStyle = 0;
+    }
+    if (insertedPlaceholder && m_typingStyle) {
+        // Apply style to the placeholder. This makes sure that the single line in the
+        // paragraph has the right height, and that the paragraph takes on the style
+        // of the preceding line and retains it even if you click away, click back, and
+        // then start typing. In this case, the typing style is applied right now, and
+        // is not retained until the next typing action.
+
+        // FIXME: is this even right? I don't think post-deletion typing style is supposed 
+        // to be saved across clicking away and clicking back, it certainly isn't in TextEdit
+
+        Position pastPlaceholder(insertedPlaceholder, 1);
+
+        setEndingSelection(Selection(m_endingPosition, m_selectionToDelete.endAffinity(), pastPlaceholder, DOWNSTREAM));
+
+        applyStyle(m_typingStyle, EditActionUnspecified);
+
+        m_typingStyle->deref();
+        m_typingStyle = 0;
+    }
+    // Set m_typingStyle as the typing style.
+    // It's perfectly OK for m_typingStyle to be null.
+    document()->part()->setTypingStyle(m_typingStyle);
+    setTypingStyle(m_typingStyle);
+}
+
+void DeleteSelectionCommand::clearTransientState()
+{
+    m_selectionToDelete.clear();
+    m_upstreamStart.clear();
+    m_downstreamStart.clear();
+    m_upstreamEnd.clear();
+    m_downstreamEnd.clear();
+    m_endingPosition.clear();
+    m_leadingWhitespace.clear();
+    m_trailingWhitespace.clear();
+
+    if (m_startBlock) {
+        m_startBlock->deref();
+        m_startBlock = 0;
+    }
+    if (m_endBlock) {
+        m_endBlock->deref();
+        m_endBlock = 0;
+    }
+    if (m_startNode) {
+        m_startNode->deref();
+        m_startNode = 0;
+    }
+    if (m_typingStyle) {
+        m_typingStyle->deref();
+        m_typingStyle = 0;
+    }
+}
+
+void DeleteSelectionCommand::doApply()
+{
+    // If selection has not been set to a custom selection when the command was created,
+    // use the current ending selection.
+    if (!m_hasSelectionToDelete)
+        m_selectionToDelete = endingSelection();
+        
+    if (!m_selectionToDelete.isRange())
+        return;
+
+    // save this to later make the selection with
+    EAffinity affinity = m_selectionToDelete.startAffinity();
+    
+    // set up our state
+    initializePositionData();
+
+    if (!m_startBlock || !m_endBlock) {
+        // Can't figure out what blocks we're in. This can happen if
+        // the document structure is not what we are expecting, like if
+        // the document has no body element, or if the editable block
+        // has been changed to display: inline. Some day it might
+        // be nice to be able to deal with this, but for now, bail.
+        clearTransientState();
+        return;
+    }
+    
+    // if all we are deleting is complete paragraph(s), we need to make
+    // sure a blank paragraph remains when we are done
+    bool forceBlankParagraph = isStartOfParagraph(VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY)) &&
+                               isEndOfParagraph(VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY));
+
+    // Delete any text that may hinder our ability to fixup whitespace after the detele
+    deleteInsignificantTextDownstream(m_trailingWhitespace);    
+
+    saveTypingStyleState();
+    insertPlaceholderForAncestorBlockContent();
+    
+    if (!handleSpecialCaseBRDelete())
+        handleGeneralDelete();
+    
+    // Do block merge if start and end of selection are in different blocks.
+    moveNodesAfterNode();
+    
+    calculateEndingPosition();
+    fixupWhitespace();
+
+    // if the m_endingPosition is already a blank paragraph, there is
+    // no need to force a new one
+    if (forceBlankParagraph &&
+        isStartOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY)) &&
+        isEndOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY))) {
+        forceBlankParagraph = false;
+    }
+    
+    NodeImpl *addedPlaceholder = forceBlankParagraph ? insertBlockPlaceholder(m_endingPosition) :
+        addBlockPlaceholderIfNeeded(m_endingPosition.node());
+
+    calculateTypingStyleAfterDelete(addedPlaceholder);
+    debugPosition("endingPosition   ", m_endingPosition);
+    setEndingSelection(Selection(m_endingPosition, affinity));
+    clearTransientState();
+    rebalanceWhitespace();
+}
+
+EditAction DeleteSelectionCommand::editingAction() const
+{
+    // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
+    // but in that case there's a TypingCommand that supplies the editingAction(), so
+    // the Undo menu correctly shows "Undo Typing"
+    return EditActionCut;
+}
+
+bool DeleteSelectionCommand::preservesTypingStyle() const
+{
+    return true;
+}
+
+} // namespace khtml
diff --git a/WebCore/khtml/editing/delete_selection_command.h b/WebCore/khtml/editing/delete_selection_command.h
new file mode 100644 (file)
index 0000000..61df503
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#ifndef __delete_selection_command_h__
+#define __delete_selection_command_h__
+
+#include "composite_edit_command.h"
+
+namespace khtml {
+
+class DeleteSelectionCommand : public CompositeEditCommand
+{ 
+public:
+    DeleteSelectionCommand(DOM::DocumentImpl *document, bool smartDelete=false, bool mergeBlocksAfterDelete=true);
+    DeleteSelectionCommand(DOM::DocumentImpl *document, const Selection &selection, bool smartDelete=false, bool mergeBlocksAfterDelete=true);
+       
+    virtual void doApply();
+    virtual EditAction editingAction() const;
+    
+private:
+    virtual bool preservesTypingStyle() const;
+
+    void initializePositionData();
+    void saveTypingStyleState();
+    void insertPlaceholderForAncestorBlockContent();
+    bool handleSpecialCaseBRDelete();
+    void handleGeneralDelete();
+    void fixupWhitespace();
+    void moveNodesAfterNode();
+    void calculateEndingPosition();
+    void calculateTypingStyleAfterDelete(DOM::NodeImpl *insertedPlaceholder);
+    void clearTransientState();
+
+    void setStartNode(DOM::NodeImpl *);
+
+    bool m_hasSelectionToDelete;
+    bool m_smartDelete;
+    bool m_mergeBlocksAfterDelete;
+    bool m_trailingWhitespaceValid;
+
+    // This data is transient and should be cleared at the end of the doApply function.
+    Selection m_selectionToDelete;
+    DOM::Position m_upstreamStart;
+    DOM::Position m_downstreamStart;
+    DOM::Position m_upstreamEnd;
+    DOM::Position m_downstreamEnd;
+    DOM::Position m_endingPosition;
+    DOM::Position m_leadingWhitespace;
+    DOM::Position m_trailingWhitespace;
+    DOM::NodeImpl *m_startBlock;
+    DOM::NodeImpl *m_endBlock;
+    DOM::NodeImpl *m_startNode;
+    DOM::CSSMutableStyleDeclarationImpl *m_typingStyle;
+};
+
+} // namespace khtml
+
+#endif // __delete_selection_command_h__
index 8f29f2535cc0e6e0bf5c4d2e5c2bc2983f7ecc14..9e3759bf63bde760fce032fb2547fa0b3654630d 100644 (file)
@@ -94,10 +94,6 @@ using DOM::TreeWalkerImpl;
 #define LOG(channel, formatAndArgs...) ((void)0)
 #define ERROR(formatAndArgs...) ((void)0)
 #define ASSERT(assertion) assert(assertion)
-#if LOG_DISABLED
-#define debugPosition(a,b) ((void)0)
-#define debugNode(a,b) ((void)0)
-#endif
 #endif
 
 namespace khtml {
@@ -107,16 +103,6 @@ static inline bool isNBSP(const QChar &c)
     return c.unicode() == 0xa0;
 }
 
-// FIXME: Can't really determine this without taking white-space mode into account.
-static inline bool nextCharacterIsCollapsibleWhitespace(const Position &pos)
-{
-    if (!pos.node())
-        return false;
-    if (!pos.node()->isTextNode())
-        return false;
-    return isCollapsibleWhitespace(static_cast<TextImpl *>(pos.node())->data()[pos.offset()]);
-}
-
 static const int spacesPerTab = 4;
 
 bool isTableStructureNode(const NodeImpl *node)
@@ -125,16 +111,6 @@ bool isTableStructureNode(const NodeImpl *node)
     return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol()));
 }
 
-static bool isListStructureNode(const NodeImpl *node)
-{
-    // FIXME: Irritating that we can get away with just going at the render tree for isTableStructureNode,
-    // but here we also have to peek at the type of DOM node?
-    RenderObject *r = node->renderer();
-    NodeImpl::Id nodeID = node->id();
-    return (r && r->isListItem())
-        || (nodeID == ID_OL || nodeID == ID_UL || nodeID == ID_DD || nodeID == ID_DT || nodeID == ID_DIR || nodeID == ID_MENU);
-}
-
 static DOMString &nonBreakingSpaceString()
 {
     static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
@@ -164,37 +140,6 @@ static int maxRangeOffset(NodeImpl *n)
     return 1;
 }
 
-static int maxDeepOffset(NodeImpl *n)
-{
-    if (n->isAtomicNode())
-        return n->caretMaxOffset();
-
-    if (n->isElementNode())
-        return n->childNodeCount();
-
-    return 1;
-}
-
-static void debugPosition(const char *prefix, const Position &pos)
-{
-    if (!prefix)
-        prefix = "";
-    if (pos.isNull())
-        LOG(Editing, "%s <null>", prefix);
-    else
-        LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
-}
-
-static void debugNode(const char *prefix, const NodeImpl *node)
-{
-    if (!prefix)
-        prefix = "";
-    if (!node)
-        LOG(Editing, "%s <null>", prefix);
-    else
-        LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node);
-}
-
 static bool isSpecialElement(NodeImpl *n)
 {
     if (!n->isHTMLElement())
@@ -223,7 +168,7 @@ static bool isSpecialElement(NodeImpl *n)
 // This version of the function is meant to be called on positions in a document fragment,
 // so it does not check for a root editable element, it is assumed these nodes will be put
 // somewhere editable in the future
-static bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos)
+bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos)
 {
     VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
 
@@ -237,7 +182,7 @@ static bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos
     return false;
 }
 
-static bool isFirstVisiblePositionInSpecialElement(const Position& pos)
+bool isFirstVisiblePositionInSpecialElement(const Position& pos)
 {
     VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
 
@@ -258,7 +203,7 @@ static Position positionBeforeNode(NodeImpl *node)
     return Position(node->parentNode(), node->nodeIndex());
 }
 
-static Position positionBeforeContainingSpecialElement(const Position& pos)
+Position positionBeforeContainingSpecialElement(const Position& pos)
 {
     ASSERT(isFirstVisiblePositionInSpecialElement(pos));
 
@@ -284,7 +229,7 @@ static Position positionBeforeContainingSpecialElement(const Position& pos)
     return result;
 }
 
-static bool isLastVisiblePositionInSpecialElement(const Position& pos)
+bool isLastVisiblePositionInSpecialElement(const Position& pos)
 {
     // make sure to get a range-compliant version of the position
     Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
@@ -308,7 +253,7 @@ static Position positionAfterNode(NodeImpl *node)
     return Position(node->parentNode(), node->nodeIndex() + 1);
 }
 
-static Position positionAfterContainingSpecialElement(const Position& pos)
+Position positionAfterContainingSpecialElement(const Position& pos)
 {
     ASSERT(isLastVisiblePositionInSpecialElement(pos));
 
@@ -337,7 +282,7 @@ static Position positionAfterContainingSpecialElement(const Position& pos)
     return result;
 }
 
-static Position positionOutsideContainingSpecialElement(const Position &pos)
+Position positionOutsideContainingSpecialElement(const Position &pos)
 {
     if (isFirstVisiblePositionInSpecialElement(pos)) {
         return positionBeforeContainingSpecialElement(pos);
@@ -348,745 +293,6 @@ static Position positionOutsideContainingSpecialElement(const Position &pos)
     return pos;
 }
 
-static Position positionBeforePossibleContainingSpecialElement(const Position &pos)
-{
-    if (isFirstVisiblePositionInSpecialElement(pos)) {
-        return positionBeforeContainingSpecialElement(pos);
-    } 
-
-    return pos;
-}
-
-static Position positionAfterPossibleContainingSpecialElement(const Position &pos)
-{
-    if (isLastVisiblePositionInSpecialElement(pos)) {
-        return positionAfterContainingSpecialElement(pos);
-    }
-
-    return pos;
-}
-
-//------------------------------------------------------------------------------------------
-// DeleteFromTextNodeCommand
-
-DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(DocumentImpl *document, TextImpl *node, long offset, long count)
-    : EditCommand(document), m_node(node), m_offset(offset), m_count(count)
-{
-    ASSERT(m_node);
-    ASSERT(m_offset >= 0);
-    ASSERT(m_offset < (long)m_node->length());
-    ASSERT(m_count >= 0);
-    
-    m_node->ref();
-}
-
-DeleteFromTextNodeCommand::~DeleteFromTextNodeCommand()
-{
-    ASSERT(m_node);
-    m_node->deref();
-}
-
-void DeleteFromTextNodeCommand::doApply()
-{
-    ASSERT(m_node);
-
-    int exceptionCode = 0;
-    m_text = m_node->substringData(m_offset, m_count, exceptionCode);
-    ASSERT(exceptionCode == 0);
-    
-    m_node->deleteData(m_offset, m_count, exceptionCode);
-    ASSERT(exceptionCode == 0);
-}
-
-void DeleteFromTextNodeCommand::doUnapply()
-{
-    ASSERT(m_node);
-    ASSERT(!m_text.isEmpty());
-
-    int exceptionCode = 0;
-    m_node->insertData(m_offset, m_text, exceptionCode);
-    ASSERT(exceptionCode == 0);
-}
-
-//------------------------------------------------------------------------------------------
-// DeleteSelectionCommand
-
-DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, bool smartDelete, bool mergeBlocksAfterDelete)
-    : CompositeEditCommand(document), 
-      m_hasSelectionToDelete(false), 
-      m_smartDelete(smartDelete), 
-      m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
-      m_startBlock(0),
-      m_endBlock(0),
-      m_startNode(0),
-      m_typingStyle(0)
-{
-}
-
-DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
-    : CompositeEditCommand(document), 
-      m_hasSelectionToDelete(true), 
-      m_smartDelete(smartDelete), 
-      m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
-      m_selectionToDelete(selection),
-      m_startBlock(0),
-      m_endBlock(0),
-      m_startNode(0),
-      m_typingStyle(0)
-{
-}
-
-void DeleteSelectionCommand::initializePositionData()
-{
-    //
-    // Handle setting some basic positions
-    //
-    Position start = m_selectionToDelete.start();
-    start = positionOutsideContainingSpecialElement(start);
-    Position end = m_selectionToDelete.end();
-    end = positionOutsideContainingSpecialElement(end);
-
-    m_upstreamStart = positionBeforePossibleContainingSpecialElement(start.upstream());
-    m_downstreamStart = positionBeforePossibleContainingSpecialElement(start.downstream());
-    m_upstreamEnd = positionAfterPossibleContainingSpecialElement(end.upstream());
-    m_downstreamEnd = positionAfterPossibleContainingSpecialElement(end.downstream());
-
-    //
-    // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
-    //
-    m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity());
-    m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
-
-    if (m_smartDelete) {
-    
-        // skip smart delete if the selection to delete already starts or ends with whitespace
-        Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()).deepEquivalent();
-        bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
-        if (!skipSmartDelete)
-            skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
-
-        // extend selection upstream if there is whitespace there
-        bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity(), true).isNotNull();
-        if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
-            VisiblePosition visiblePos = VisiblePosition(start, m_selectionToDelete.startAffinity()).previous();
-            pos = visiblePos.deepEquivalent();
-            // Expand out one character upstream for smart delete and recalculate
-            // positions based on this change.
-            m_upstreamStart = pos.upstream();
-            m_downstreamStart = pos.downstream();
-            m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
-        }
-        
-        // trailing whitespace is only considered for smart delete if there is no leading
-        // whitespace, as in the case where you double-click the first word of a paragraph.
-        if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
-            // Expand out one character downstream for smart delete and recalculate
-            // positions based on this change.
-            pos = VisiblePosition(end, m_selectionToDelete.endAffinity()).next().deepEquivalent();
-            m_upstreamEnd = pos.upstream();
-            m_downstreamEnd = pos.downstream();
-            m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
-        }
-    }
-    
-    m_trailingWhitespaceValid = true;
-    
-    //
-    // Handle setting start and end blocks and the start node.
-    //
-    m_startBlock = m_downstreamStart.node()->enclosingBlockFlowElement();
-    m_startBlock->ref();
-    m_endBlock = m_upstreamEnd.node()->enclosingBlockFlowElement();
-    m_endBlock->ref();
-    m_startNode = m_upstreamStart.node();
-    m_startNode->ref();
-
-    //
-    // Handle detecting if the line containing the selection end is itself fully selected.
-    // This is one of the tests that determines if block merging of content needs to be done.
-    //
-    VisiblePosition visibleEnd(end, m_selectionToDelete.endAffinity());
-    if (isStartOfParagraph(visibleEnd) || isEndOfParagraph(visibleEnd)) {
-        Position previousLineStart = previousLinePosition(visibleEnd, 0).deepEquivalent();
-        if (previousLineStart.isNull() || RangeImpl::compareBoundaryPoints(previousLineStart, m_downstreamStart) >= 0)
-            m_mergeBlocksAfterDelete = false;
-    }
-
-    debugPosition("m_upstreamStart      ", m_upstreamStart);
-    debugPosition("m_downstreamStart    ", m_downstreamStart);
-    debugPosition("m_upstreamEnd        ", m_upstreamEnd);
-    debugPosition("m_downstreamEnd      ", m_downstreamEnd);
-    debugPosition("m_leadingWhitespace  ", m_leadingWhitespace);
-    debugPosition("m_trailingWhitespace ", m_trailingWhitespace);
-    debugNode(    "m_startBlock         ", m_startBlock);
-    debugNode(    "m_endBlock           ", m_endBlock);    
-    debugNode(    "m_startNode          ", m_startNode);    
-}
-
-void DeleteSelectionCommand::insertPlaceholderForAncestorBlockContent()
-{
-    // This code makes sure a line does not disappear when deleting in this case:
-    // <p>foo</p>bar<p>baz</p>
-    // Select "bar" and hit delete. If nothing is done, the line containing bar will disappear.
-    // It needs to be held open by inserting a placeholder.
-    // Also see:
-    // <rdar://problem/3928305> selecting an entire line and typing over causes new inserted text at top of document
-    //
-    // The checks below detect the case where the selection contains content in an ancestor block 
-    // surrounded by child blocks.
-    //
-    VisiblePosition visibleStart(m_upstreamStart, VP_DEFAULT_AFFINITY);
-    VisiblePosition beforeStart = visibleStart.previous();
-    NodeImpl *startBlock = enclosingBlockFlowElement(visibleStart);
-    NodeImpl *beforeStartBlock = enclosingBlockFlowElement(beforeStart);
-    
-    if (!beforeStart.isNull() &&
-        !inSameBlock(visibleStart, beforeStart) &&
-        beforeStartBlock->isAncestor(startBlock) &&
-        startBlock != m_upstreamStart.node()) {
-
-        VisiblePosition visibleEnd(m_downstreamEnd, VP_DEFAULT_AFFINITY);
-        VisiblePosition afterEnd = visibleEnd.next();
-        
-        if ((!afterEnd.isNull() && !inSameBlock(afterEnd, visibleEnd) && !inSameBlock(afterEnd, visibleStart)) ||
-            (m_downstreamEnd == m_selectionToDelete.end() && isEndOfParagraph(visibleEnd))) {
-            NodeImpl *block = createDefaultParagraphElement(document());
-            insertNodeBefore(block, m_upstreamStart.node());
-            addBlockPlaceholderIfNeeded(block);
-            m_endingPosition = Position(block, 0);
-        }
-    }
-}
-
-void DeleteSelectionCommand::saveTypingStyleState()
-{
-    // Figure out the typing style in effect before the delete is done.
-    // FIXME: Improve typing style.
-    // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
-    CSSComputedStyleDeclarationImpl *computedStyle = m_selectionToDelete.start().computedStyle();
-    computedStyle->ref();
-    m_typingStyle = computedStyle->copyInheritableProperties();
-    m_typingStyle->ref();
-    computedStyle->deref();
-}
-
-bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
-{
-    // Check for special-case where the selection contains only a BR on a line by itself after another BR.
-    bool upstreamStartIsBR = m_startNode->id() == ID_BR;
-    bool downstreamStartIsBR = m_downstreamStart.node()->id() == ID_BR;
-    bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
-    if (isBROnLineByItself) {
-        removeNode(m_downstreamStart.node());
-        m_endingPosition = m_upstreamStart;
-        m_mergeBlocksAfterDelete = false;
-        return true;
-    }
-
-    // Not a special-case delete per se, but we can detect that the merging of content between blocks
-    // should not be done.
-    if (upstreamStartIsBR && downstreamStartIsBR)
-        m_mergeBlocksAfterDelete = false;
-
-    return false;
-}
-
-void DeleteSelectionCommand::setStartNode(NodeImpl *node)
-{
-    NodeImpl *old = m_startNode;
-    m_startNode = node;
-    if (m_startNode)
-        m_startNode->ref();
-    if (old)
-        old->deref();
-}
-
-void DeleteSelectionCommand::handleGeneralDelete()
-{
-    int startOffset = m_upstreamStart.offset();
-    VisiblePosition visibleEnd = VisiblePosition(m_downstreamEnd, m_selectionToDelete.endAffinity());
-    bool endAtEndOfBlock = isEndOfBlock(visibleEnd);
-
-    // Handle some special cases where the selection begins and ends on specific visible units.
-    // Sometimes a node that is actually selected needs to be retained in order to maintain
-    // user expectations for the delete operation. Here is an example:
-    //     1. Open a new Blot or Mail document
-    //     2. hit Return ten times or so
-    //     3. Type a letter (do not hit Return after it)
-    //     4. Type shift-up-arrow to select the line containing the letter and the previous blank line
-    //     5. Hit Delete
-    // You expect the insertion point to wind up at the start of the line where your selection began.
-    // Because of the nature of HTML, the editing code needs to perform a special check to get
-    // this behavior. So:
-    // If the entire start block is selected, and the selection does not extend to the end of the 
-    // end of a block other than the block containing the selection start, then do not delete the 
-    // start block, otherwise delete the start block.
-    // A similar case is provided to cover selections starting in BR elements.
-    if (startOffset == 1 && m_startNode && m_startNode->id() == ID_BR) {
-        setStartNode(m_startNode->traverseNextNode());
-        startOffset = 0;
-    }
-    if (m_startBlock != m_endBlock && startOffset == 0 && m_startNode && m_startNode->id() == ID_BR && endAtEndOfBlock) {
-        // Don't delete the BR element
-        setStartNode(m_startNode->traverseNextNode());
-    }
-    else if (m_startBlock != m_endBlock && isStartOfBlock(VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()))) {
-        if (!m_startBlock->isAncestor(m_endBlock) && !isStartOfBlock(visibleEnd) && endAtEndOfBlock) {
-            // Delete all the children of the block, but not the block itself.
-            setStartNode(m_startBlock->firstChild());
-            startOffset = 0;
-        }
-    }
-    else if (startOffset >= m_startNode->caretMaxOffset() &&
-             (m_startNode->isAtomicNode() || startOffset == 0)) {
-        // Move the start node to the next node in the tree since the startOffset is equal to
-        // or beyond the start node's caretMaxOffset This means there is nothing visible to delete. 
-        // But don't do this if the node is not atomic - we don't want to move into the first child.
-
-        // Also, before moving on, delete any insignificant text that may be present in a text node.
-        if (m_startNode->isTextNode()) {
-            // Delete any insignificant text from this node.
-            TextImpl *text = static_cast<TextImpl *>(m_startNode);
-            if (text->length() > (unsigned)m_startNode->caretMaxOffset())
-                deleteTextFromNode(text, m_startNode->caretMaxOffset(), text->length() - m_startNode->caretMaxOffset());
-        }
-        
-        // shift the start node to the next
-        setStartNode(m_startNode->traverseNextNode());
-        startOffset = 0;
-    }
-
-    // Done adjusting the start.  See if we're all done.
-    if (!m_startNode)
-        return;
-
-    if (m_startNode == m_downstreamEnd.node()) {
-        // The selection to delete is all in one node.
-        if (!m_startNode->renderer() || 
-            (startOffset == 0 && m_downstreamEnd.offset() >= maxDeepOffset(m_startNode))) {
-            // just delete
-            removeFullySelectedNode(m_startNode);
-        } else if (m_downstreamEnd.offset() - startOffset > 0) {
-            if (m_startNode->isTextNode()) {
-                // in a text node that needs to be trimmed
-                TextImpl *text = static_cast<TextImpl *>(m_startNode);
-                deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset);
-                m_trailingWhitespaceValid = false;
-            } else {
-                removeChildrenInRange(m_startNode, startOffset, m_downstreamEnd.offset());
-                m_endingPosition = m_upstreamStart;
-            }
-        }
-    }
-    else {
-        // The selection to delete spans more than one node.
-        NodeImpl *node = m_startNode;
-        
-        if (startOffset > 0) {
-            if (m_startNode->isTextNode()) {
-                // in a text node that needs to be trimmed
-                TextImpl *text = static_cast<TextImpl *>(node);
-                deleteTextFromNode(text, startOffset, text->length() - startOffset);
-                node = node->traverseNextNode();
-            } else {
-                node = m_startNode->childNode(startOffset);
-            }
-        }
-        
-        // handle deleting all nodes that are completely selected
-        while (node && node != m_downstreamEnd.node()) {
-            if (RangeImpl::compareBoundaryPoints(Position(node, 0), m_downstreamEnd) >= 0) {
-                // traverseNextSibling just blew past the end position, so stop deleting
-                node = 0;
-            } else if (!m_downstreamEnd.node()->isAncestor(node)) {
-                NodeImpl *nextNode = node->traverseNextSibling();
-                // if we just removed a node from the end container, update end position so the
-                // check above will work
-                if (node->parentNode() == m_downstreamEnd.node()) {
-                    ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.offset());
-                    m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.offset() - 1);
-                }
-                removeFullySelectedNode(node);
-                node = nextNode;
-            } else {
-                NodeImpl *n = node->lastChild();
-                while (n && n->lastChild())
-                    n = n->lastChild();
-                if (n == m_downstreamEnd.node() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMaxOffset()) {
-                    removeFullySelectedNode(node);
-                    m_trailingWhitespaceValid = false;
-                    node = 0;
-                } 
-                else {
-                    node = node->traverseNextNode();
-                }
-            }
-        }
-
-        
-        if (m_downstreamEnd.node() != m_startNode && !m_upstreamStart.node()->isAncestor(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMinOffset()) {
-            if (m_downstreamEnd.offset() >= maxDeepOffset(m_downstreamEnd.node())) {
-                // need to delete whole node
-                // we can get here if this is the last node in the block
-                // remove an ancestor of m_downstreamEnd.node(), and thus m_downstreamEnd.node() itself
-                if (!m_upstreamStart.node()->inDocument() ||
-                    m_upstreamStart.node() == m_downstreamEnd.node() ||
-                    m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
-                    m_upstreamStart = Position(m_downstreamEnd.node()->parentNode(), m_downstreamEnd.node()->nodeIndex());
-                }
-                
-                removeFullySelectedNode(m_downstreamEnd.node());
-                m_trailingWhitespaceValid = false;
-            } else {
-                if (m_downstreamEnd.node()->isTextNode()) {
-                    // in a text node that needs to be trimmed
-                    TextImpl *text = static_cast<TextImpl *>(m_downstreamEnd.node());
-                    if (m_downstreamEnd.offset() > 0) {
-                        deleteTextFromNode(text, 0, m_downstreamEnd.offset());
-                        m_downstreamEnd = Position(text, 0);
-                        m_trailingWhitespaceValid = false;
-                    }
-                } else {
-                    int offset = 0;
-                    if (m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
-                        NodeImpl *n = m_upstreamStart.node();
-                        while (n && n->parentNode() != m_downstreamEnd.node())
-                            n = n->parentNode();
-                        if (n)
-                            offset = n->nodeIndex() + 1;
-                    }
-                    removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.offset());
-                    m_downstreamEnd = Position(m_downstreamEnd.node(), offset);
-                }
-            }
-        }
-    }
-}
-
-void DeleteSelectionCommand::fixupWhitespace()
-{
-    document()->updateLayout();
-    if (m_leadingWhitespace.isNotNull() && (m_trailingWhitespace.isNotNull() || !m_leadingWhitespace.isRenderedCharacter())) {
-        LOG(Editing, "replace leading");
-        TextImpl *textNode = static_cast<TextImpl *>(m_leadingWhitespace.node());
-        replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
-    }
-    else if (m_trailingWhitespace.isNotNull()) {
-        if (m_trailingWhitespaceValid) {
-            if (!m_trailingWhitespace.isRenderedCharacter()) {
-                LOG(Editing, "replace trailing [valid]");
-                TextImpl *textNode = static_cast<TextImpl *>(m_trailingWhitespace.node());
-                replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
-            }
-        }
-        else {
-            Position pos = m_endingPosition.downstream();
-            pos = Position(pos.node(), pos.offset() - 1);
-            if (nextCharacterIsCollapsibleWhitespace(pos) && !pos.isRenderedCharacter()) {
-                LOG(Editing, "replace trailing [invalid]");
-                TextImpl *textNode = static_cast<TextImpl *>(pos.node());
-                replaceTextInNode(textNode, pos.offset(), 1, nonBreakingSpaceString());
-                // need to adjust ending position since the trailing position is not valid.
-                m_endingPosition = pos;
-            }
-        }
-    }
-}
-
-// This function moves nodes in the block containing startNode to dstBlock, starting
-// from startNode and proceeding to the end of the paragraph. Nodes in the block containing
-// startNode that appear in document order before startNode are not moved.
-// This function is an important helper for deleting selections that cross paragraph
-// boundaries.
-void DeleteSelectionCommand::moveNodesAfterNode()
-{
-    if (!m_mergeBlocksAfterDelete)
-        return;
-
-    if (m_endBlock == m_startBlock)
-        return;
-
-    NodeImpl *startNode = m_downstreamEnd.node();
-    NodeImpl *dstNode = m_upstreamStart.node();
-
-    if (!startNode->inDocument() || !dstNode->inDocument())
-        return;
-
-    NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
-    if (isTableStructureNode(startBlock) || isListStructureNode(startBlock))
-        // Do not move content between parts of a table or list.
-        return;
-
-    // Now that we are about to add content, check to see if a placeholder element
-    // can be removed.
-    removeBlockPlaceholder(startBlock);
-
-    // Move the subtree containing node
-    NodeImpl *node = startNode->enclosingInlineElement();
-
-    // Insert after the subtree containing destNode
-    NodeImpl *refNode = dstNode->enclosingInlineElement();
-
-    // Nothing to do if start is already at the beginning of dstBlock
-    NodeImpl *dstBlock = refNode->enclosingBlockFlowElement();
-    if (startBlock == dstBlock->firstChild())
-        return;
-
-    // Do the move.
-    NodeImpl *rootNode = refNode->rootEditableElement();
-    while (node && node->isAncestor(startBlock)) {
-        NodeImpl *moveNode = node;
-        node = node->nextSibling();
-        removeNode(moveNode);
-        if (moveNode->id() == ID_BR && !moveNode->renderer()) {
-            // Just remove this node, and don't put it back.
-            // If the BR was not rendered (since it was at the end of a block, for instance), 
-            // putting it back in the document might make it appear, and that is not desirable.
-            break;
-        }
-        if (refNode == rootNode)
-            insertNodeAt(moveNode, refNode, 0);
-        else
-            insertNodeAfter(moveNode, refNode);
-        refNode = moveNode;
-        if (moveNode->id() == ID_BR)
-            break;
-    }
-
-    // If the startBlock no longer has any kids, we may need to deal with adding a BR
-    // to make the layout come out right. Consider this document:
-    //
-    // One
-    // <div>Two</div>
-    // Three
-    // 
-    // Placing the insertion before before the 'T' of 'Two' and hitting delete will
-    // move the contents of the div to the block containing 'One' and delete the div.
-    // This will have the side effect of moving 'Three' on to the same line as 'One'
-    // and 'Two'. This is undesirable. We fix this up by adding a BR before the 'Three'.
-    // This may not be ideal, but it is better than nothing.
-    document()->updateLayout();
-    if (!startBlock->renderer() || !startBlock->renderer()->firstChild()) {
-        removeNode(startBlock);
-        document()->updateLayout();
-        if (refNode->renderer() && refNode->renderer()->inlineBox() && refNode->renderer()->inlineBox()->nextOnLineExists()) {
-            insertNodeAfter(createBreakElement(document()), refNode);
-        }
-    }
-}
-
-void DeleteSelectionCommand::calculateEndingPosition()
-{
-    if (m_endingPosition.isNotNull() && m_endingPosition.node()->inDocument())
-        return;
-
-    m_endingPosition = m_upstreamStart;
-    if (m_endingPosition.node()->inDocument())
-        return;
-    
-    m_endingPosition = m_downstreamEnd;
-    if (m_endingPosition.node()->inDocument())
-        return;
-
-    m_endingPosition = Position(m_startBlock, 0);
-    if (m_endingPosition.node()->inDocument())
-        return;
-
-    m_endingPosition = Position(m_endBlock, 0);
-    if (m_endingPosition.node()->inDocument())
-        return;
-
-    m_endingPosition = Position(document()->documentElement(), 0);
-}
-
-void DeleteSelectionCommand::calculateTypingStyleAfterDelete(NodeImpl *insertedPlaceholder)
-{
-    // Compute the difference between the style before the delete and the style now
-    // after the delete has been done. Set this style on the part, so other editing
-    // commands being composed with this one will work, and also cache it on the command,
-    // so the KHTMLPart::appliedEditing can set it after the whole composite command 
-    // has completed.
-    // FIXME: Improve typing style.
-    // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
-    CSSComputedStyleDeclarationImpl endingStyle(m_endingPosition.node());
-    endingStyle.diff(m_typingStyle);
-    if (!m_typingStyle->length()) {
-        m_typingStyle->deref();
-        m_typingStyle = 0;
-    }
-    if (insertedPlaceholder && m_typingStyle) {
-        // Apply style to the placeholder. This makes sure that the single line in the
-        // paragraph has the right height, and that the paragraph takes on the style
-        // of the preceding line and retains it even if you click away, click back, and
-        // then start typing. In this case, the typing style is applied right now, and
-        // is not retained until the next typing action.
-
-        // FIXME: is this even right? I don't think post-deletion typing style is supposed 
-        // to be saved across clicking away and clicking back, it certainly isn't in TextEdit
-
-        Position pastPlaceholder(insertedPlaceholder, 1);
-
-        setEndingSelection(Selection(m_endingPosition, m_selectionToDelete.endAffinity(), pastPlaceholder, DOWNSTREAM));
-
-        applyStyle(m_typingStyle, EditActionUnspecified);
-
-        m_typingStyle->deref();
-        m_typingStyle = 0;
-    }
-    // Set m_typingStyle as the typing style.
-    // It's perfectly OK for m_typingStyle to be null.
-    document()->part()->setTypingStyle(m_typingStyle);
-    setTypingStyle(m_typingStyle);
-}
-
-void DeleteSelectionCommand::clearTransientState()
-{
-    m_selectionToDelete.clear();
-    m_upstreamStart.clear();
-    m_downstreamStart.clear();
-    m_upstreamEnd.clear();
-    m_downstreamEnd.clear();
-    m_endingPosition.clear();
-    m_leadingWhitespace.clear();
-    m_trailingWhitespace.clear();
-
-    if (m_startBlock) {
-        m_startBlock->deref();
-        m_startBlock = 0;
-    }
-    if (m_endBlock) {
-        m_endBlock->deref();
-        m_endBlock = 0;
-    }
-    if (m_startNode) {
-        m_startNode->deref();
-        m_startNode = 0;
-    }
-    if (m_typingStyle) {
-        m_typingStyle->deref();
-        m_typingStyle = 0;
-    }
-}
-
-void DeleteSelectionCommand::doApply()
-{
-    // If selection has not been set to a custom selection when the command was created,
-    // use the current ending selection.
-    if (!m_hasSelectionToDelete)
-        m_selectionToDelete = endingSelection();
-        
-    if (!m_selectionToDelete.isRange())
-        return;
-
-    // save this to later make the selection with
-    EAffinity affinity = m_selectionToDelete.startAffinity();
-    
-    // set up our state
-    initializePositionData();
-
-    if (!m_startBlock || !m_endBlock) {
-        // Can't figure out what blocks we're in. This can happen if
-        // the document structure is not what we are expecting, like if
-        // the document has no body element, or if the editable block
-        // has been changed to display: inline. Some day it might
-        // be nice to be able to deal with this, but for now, bail.
-        clearTransientState();
-        return;
-    }
-    
-    // if all we are deleting is complete paragraph(s), we need to make
-    // sure a blank paragraph remains when we are done
-    bool forceBlankParagraph = isStartOfParagraph(VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY)) &&
-                               isEndOfParagraph(VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY));
-
-    // Delete any text that may hinder our ability to fixup whitespace after the detele
-    deleteInsignificantTextDownstream(m_trailingWhitespace);    
-
-    saveTypingStyleState();
-    insertPlaceholderForAncestorBlockContent();
-    
-    if (!handleSpecialCaseBRDelete())
-        handleGeneralDelete();
-    
-    // Do block merge if start and end of selection are in different blocks.
-    moveNodesAfterNode();
-    
-    calculateEndingPosition();
-    fixupWhitespace();
-
-    // if the m_endingPosition is already a blank paragraph, there is
-    // no need to force a new one
-    if (forceBlankParagraph &&
-        isStartOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY)) &&
-        isEndOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY))) {
-        forceBlankParagraph = false;
-    }
-    
-    NodeImpl *addedPlaceholder = forceBlankParagraph ? insertBlockPlaceholder(m_endingPosition) :
-        addBlockPlaceholderIfNeeded(m_endingPosition.node());
-
-    calculateTypingStyleAfterDelete(addedPlaceholder);
-    debugPosition("endingPosition   ", m_endingPosition);
-    setEndingSelection(Selection(m_endingPosition, affinity));
-    clearTransientState();
-    rebalanceWhitespace();
-}
-
-EditAction DeleteSelectionCommand::editingAction() const
-{
-    // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
-    // but in that case there's a TypingCommand that supplies the editingAction(), so
-    // the Undo menu correctly shows "Undo Typing"
-    return EditActionCut;
-}
-
-bool DeleteSelectionCommand::preservesTypingStyle() const
-{
-    return true;
-}
-
-//------------------------------------------------------------------------------------------
-// InsertIntoTextNode
-
-InsertIntoTextNode::InsertIntoTextNode(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
-    : EditCommand(document), m_node(node), m_offset(offset)
-{
-    ASSERT(m_node);
-    ASSERT(m_offset >= 0);
-    ASSERT(!text.isEmpty());
-    
-    m_node->ref();
-    m_text = text.copy(); // make a copy to ensure that the string never changes
-}
-
-InsertIntoTextNode::~InsertIntoTextNode()
-{
-    if (m_node)
-        m_node->deref();
-}
-
-void InsertIntoTextNode::doApply()
-{
-    ASSERT(m_node);
-    ASSERT(m_offset >= 0);
-    ASSERT(!m_text.isEmpty());
-
-    int exceptionCode = 0;
-    m_node->insertData(m_offset, m_text, exceptionCode);
-    ASSERT(exceptionCode == 0);
-}
-
-void InsertIntoTextNode::doUnapply()
-{
-    ASSERT(m_node);
-    ASSERT(m_offset >= 0);
-    ASSERT(!m_text.isEmpty());
-
-    int exceptionCode = 0;
-    m_node->deleteData(m_offset, m_text.length(), exceptionCode);
-    ASSERT(exceptionCode == 0);
-}
-
 //------------------------------------------------------------------------------------------
 // InsertLineBreakCommand
 
@@ -1231,50 +437,6 @@ void InsertLineBreakCommand::doApply()
     rebalanceWhitespace();
 }
 
-//------------------------------------------------------------------------------------------
-// InsertNodeBeforeCommand
-
-InsertNodeBeforeCommand::InsertNodeBeforeCommand(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
-    : EditCommand(document), m_insertChild(insertChild), m_refChild(refChild)
-{
-    ASSERT(m_insertChild);
-    m_insertChild->ref();
-
-    ASSERT(m_refChild);
-    m_refChild->ref();
-}
-
-InsertNodeBeforeCommand::~InsertNodeBeforeCommand()
-{
-    ASSERT(m_insertChild);
-    m_insertChild->deref();
-
-    ASSERT(m_refChild);
-    m_refChild->deref();
-}
-
-void InsertNodeBeforeCommand::doApply()
-{
-    ASSERT(m_insertChild);
-    ASSERT(m_refChild);
-    ASSERT(m_refChild->parentNode());
-
-    int exceptionCode = 0;
-    m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
-    ASSERT(exceptionCode == 0);
-}
-
-void InsertNodeBeforeCommand::doUnapply()
-{
-    ASSERT(m_insertChild);
-    ASSERT(m_refChild);
-    ASSERT(m_refChild->parentNode());
-
-    int exceptionCode = 0;
-    m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
-    ASSERT(exceptionCode == 0);
-}
-
 //------------------------------------------------------------------------------------------
 // InsertParagraphSeparatorCommand
 
index e1b05df385808e242d7e6859a8156d0726cdb943..eb8d69921d8456fcff61b2329db706af31206f3a 100644 (file)
@@ -29,6 +29,7 @@
 #include "edit_command.h"
 #include "composite_edit_command.h"
 #include "apply_style_command.h"
+#include "delete_selection_command.h"
 
 #include "dom_nodeimpl.h"
 #include "editing/edit_actions.h"
@@ -52,119 +53,6 @@ namespace khtml {
 class Selection;
 class VisiblePosition;
 
-//------------------------------------------------------------------------------------------
-// DeleteFromTextNodeCommand
-
-class DeleteFromTextNodeCommand : public EditCommand
-{
-public:
-    DeleteFromTextNodeCommand(DOM::DocumentImpl *document, DOM::TextImpl *node, long offset, long count);
-    virtual ~DeleteFromTextNodeCommand();
-       
-    virtual void doApply();
-    virtual void doUnapply();
-
-    DOM::TextImpl *node() const { return m_node; }
-    long offset() const { return m_offset; }
-    long count() const { return m_count; }
-
-private:
-    DOM::TextImpl *m_node;
-    long m_offset;
-    long m_count;
-    DOM::DOMString m_text;
-};
-
-//------------------------------------------------------------------------------------------
-// DeleteSelectionCommand
-
-class DeleteSelectionCommand : public CompositeEditCommand
-{ 
-public:
-    DeleteSelectionCommand(DOM::DocumentImpl *document, bool smartDelete=false, bool mergeBlocksAfterDelete=true);
-    DeleteSelectionCommand(DOM::DocumentImpl *document, const Selection &selection, bool smartDelete=false, bool mergeBlocksAfterDelete=true);
-       
-    virtual void doApply();
-    virtual EditAction editingAction() const;
-    
-private:
-    virtual bool preservesTypingStyle() const;
-
-    void initializePositionData();
-    void saveTypingStyleState();
-    void insertPlaceholderForAncestorBlockContent();
-    bool handleSpecialCaseBRDelete();
-    void handleGeneralDelete();
-    void fixupWhitespace();
-    void moveNodesAfterNode();
-    void calculateEndingPosition();
-    void calculateTypingStyleAfterDelete(DOM::NodeImpl *insertedPlaceholder);
-    void clearTransientState();
-
-    void setStartNode(DOM::NodeImpl *);
-
-    bool m_hasSelectionToDelete;
-    bool m_smartDelete;
-    bool m_mergeBlocksAfterDelete;
-    bool m_trailingWhitespaceValid;
-
-    // This data is transient and should be cleared at the end of the doApply function.
-    Selection m_selectionToDelete;
-    DOM::Position m_upstreamStart;
-    DOM::Position m_downstreamStart;
-    DOM::Position m_upstreamEnd;
-    DOM::Position m_downstreamEnd;
-    DOM::Position m_endingPosition;
-    DOM::Position m_leadingWhitespace;
-    DOM::Position m_trailingWhitespace;
-    DOM::NodeImpl *m_startBlock;
-    DOM::NodeImpl *m_endBlock;
-    DOM::NodeImpl *m_startNode;
-    DOM::CSSMutableStyleDeclarationImpl *m_typingStyle;
-};
-
-//------------------------------------------------------------------------------------------
-// InsertIntoTextNode
-
-class InsertIntoTextNode : public EditCommand
-{
-public:
-    InsertIntoTextNode(DOM::DocumentImpl *document, DOM::TextImpl *, long, const DOM::DOMString &);
-    virtual ~InsertIntoTextNode();
-       
-    virtual void doApply();
-    virtual void doUnapply();
-
-    DOM::TextImpl *node() const { return m_node; }
-    long offset() const { return m_offset; }
-    DOM::DOMString text() const { return m_text; }
-
-private:
-    DOM::TextImpl *m_node;
-    long m_offset;
-    DOM::DOMString m_text;
-};
-
-//------------------------------------------------------------------------------------------
-// InsertNodeBeforeCommand
-
-class InsertNodeBeforeCommand : public EditCommand
-{
-public:
-    InsertNodeBeforeCommand(DOM::DocumentImpl *, DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
-    virtual ~InsertNodeBeforeCommand();
-
-    virtual void doApply();
-    virtual void doUnapply();
-
-    DOM::NodeImpl *insertChild() const { return m_insertChild; }
-    DOM::NodeImpl *refChild() const { return m_refChild; }
-
-private:
-    DOM::NodeImpl *m_insertChild;
-    DOM::NodeImpl *m_refChild; 
-};
-
 //------------------------------------------------------------------------------------------
 // InsertLineBreakCommand
 
@@ -691,6 +579,12 @@ bool isMailPasteAsQuotationNode(const DOM::NodeImpl *node);
 bool isTableStructureNode(const DOM::NodeImpl *node);
 DOM::ElementImpl *createBlockPlaceholderElement(DOM::DocumentImpl *document);
 
+bool isFirstVisiblePositionInSpecialElement(const DOM::Position& pos);
+DOM::Position positionBeforeContainingSpecialElement(const DOM::Position& pos);
+bool isLastVisiblePositionInSpecialElement(const DOM::Position& pos);
+DOM::Position positionAfterContainingSpecialElement(const DOM::Position& pos);
+DOM::Position positionOutsideContainingSpecialElement(const DOM::Position &pos);
+
 } // end namespace khtml
 
 #endif
diff --git a/WebCore/khtml/editing/insert_into_text_node_command.cpp b/WebCore/khtml/editing/insert_into_text_node_command.cpp
new file mode 100644 (file)
index 0000000..e8d5e39
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "insert_into_text_node_command.h"
+
+#include "xml/dom_textimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::TextImpl;
+using DOM::DOMString;
+
+namespace khtml {
+
+InsertIntoTextNodeCommand::InsertIntoTextNodeCommand(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
+    : EditCommand(document), m_node(node), m_offset(offset)
+{
+    ASSERT(m_node);
+    ASSERT(m_offset >= 0);
+    ASSERT(!text.isEmpty());
+    
+    m_node->ref();
+    m_text = text.copy(); // make a copy to ensure that the string never changes
+}
+
+InsertIntoTextNodeCommand::~InsertIntoTextNodeCommand()
+{
+    if (m_node)
+        m_node->deref();
+}
+
+void InsertIntoTextNodeCommand::doApply()
+{
+    ASSERT(m_node);
+    ASSERT(m_offset >= 0);
+    ASSERT(!m_text.isEmpty());
+
+    int exceptionCode = 0;
+    m_node->insertData(m_offset, m_text, exceptionCode);
+    ASSERT(exceptionCode == 0);
+}
+
+void InsertIntoTextNodeCommand::doUnapply()
+{
+    ASSERT(m_node);
+    ASSERT(m_offset >= 0);
+    ASSERT(!m_text.isEmpty());
+
+    int exceptionCode = 0;
+    m_node->deleteData(m_offset, m_text.length(), exceptionCode);
+    ASSERT(exceptionCode == 0);
+}
+
+} // namespace khtml
+
diff --git a/WebCore/khtml/editing/insert_into_text_node_command.h b/WebCore/khtml/editing/insert_into_text_node_command.h
new file mode 100644 (file)
index 0000000..b6f7b35
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#ifndef __insert_into_text_node_command_h__
+#define __insert_into_text_node_command_h__
+
+#include "edit_command.h"
+
+#include "dom/dom_string.h"
+
+namespace DOM {
+    class TextImpl;
+    class DOMString;
+}
+
+namespace khtml {
+
+class InsertIntoTextNodeCommand : public EditCommand
+{
+public:
+    InsertIntoTextNodeCommand(DOM::DocumentImpl *document, DOM::TextImpl *, long, const DOM::DOMString &);
+    virtual ~InsertIntoTextNodeCommand();
+       
+    virtual void doApply();
+    virtual void doUnapply();
+
+    DOM::TextImpl *node() const { return m_node; }
+    long offset() const { return m_offset; }
+    DOM::DOMString text() const { return m_text; }
+
+private:
+    DOM::TextImpl *m_node;
+    long m_offset;
+    DOM::DOMString m_text;
+};
+
+} // namespace khtml
+
+#endif // __insert_into_text_node_command_h__
diff --git a/WebCore/khtml/editing/insert_node_before_command.cpp b/WebCore/khtml/editing/insert_node_before_command.cpp
new file mode 100644 (file)
index 0000000..e0b0422
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "insert_node_before_command.h"
+
+#include "xml/dom_nodeimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::NodeImpl;
+
+namespace khtml {
+
+InsertNodeBeforeCommand::InsertNodeBeforeCommand(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
+    : EditCommand(document), m_insertChild(insertChild), m_refChild(refChild)
+{
+    ASSERT(m_insertChild);
+    m_insertChild->ref();
+
+    ASSERT(m_refChild);
+    m_refChild->ref();
+}
+
+InsertNodeBeforeCommand::~InsertNodeBeforeCommand()
+{
+    ASSERT(m_insertChild);
+    m_insertChild->deref();
+
+    ASSERT(m_refChild);
+    m_refChild->deref();
+}
+
+void InsertNodeBeforeCommand::doApply()
+{
+    ASSERT(m_insertChild);
+    ASSERT(m_refChild);
+    ASSERT(m_refChild->parentNode());
+
+    int exceptionCode = 0;
+    m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
+    ASSERT(exceptionCode == 0);
+}
+
+void InsertNodeBeforeCommand::doUnapply()
+{
+    ASSERT(m_insertChild);
+    ASSERT(m_refChild);
+    ASSERT(m_refChild->parentNode());
+
+    int exceptionCode = 0;
+    m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
+    ASSERT(exceptionCode == 0);
+}
+
+}
diff --git a/WebCore/khtml/editing/insert_node_before_command.h b/WebCore/khtml/editing/insert_node_before_command.h
new file mode 100644 (file)
index 0000000..df6f42b
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#ifndef __insert_node_before_command_h__
+#define __insert_node_before_command_h__
+
+#include "edit_command.h"
+
+namespace khtml {
+
+class InsertNodeBeforeCommand : public EditCommand
+{
+public:
+    InsertNodeBeforeCommand(DOM::DocumentImpl *, DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
+    virtual ~InsertNodeBeforeCommand();
+
+    virtual void doApply();
+    virtual void doUnapply();
+
+    DOM::NodeImpl *insertChild() const { return m_insertChild; }
+    DOM::NodeImpl *refChild() const { return m_refChild; }
+
+private:
+    DOM::NodeImpl *m_insertChild;
+    DOM::NodeImpl *m_refChild; 
+};
+
+} // namespace khtml
+
+#endif // __insert_node_before_command_h__