+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.
//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,
#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"
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);
}
{
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);
}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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__
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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__
#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 {
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)
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));
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())
// 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);
return false;
}
-static bool isFirstVisiblePositionInSpecialElement(const Position& pos)
+bool isFirstVisiblePositionInSpecialElement(const Position& pos)
{
VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
return Position(node->parentNode(), node->nodeIndex());
}
-static Position positionBeforeContainingSpecialElement(const Position& pos)
+Position positionBeforeContainingSpecialElement(const Position& pos)
{
ASSERT(isFirstVisiblePositionInSpecialElement(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();
return Position(node->parentNode(), node->nodeIndex() + 1);
}
-static Position positionAfterContainingSpecialElement(const Position& pos)
+Position positionAfterContainingSpecialElement(const Position& pos)
{
ASSERT(isLastVisiblePositionInSpecialElement(pos));
return result;
}
-static Position positionOutsideContainingSpecialElement(const Position &pos)
+Position positionOutsideContainingSpecialElement(const Position &pos)
{
if (isFirstVisiblePositionInSpecialElement(pos)) {
return positionBeforeContainingSpecialElement(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
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
#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"
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
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
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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__
--- /dev/null
+/*
+ * 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);
+}
+
+}
--- /dev/null
+/*
+ * 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__