diff options
Diffstat (limited to 'WebCore/editing/CompositeEditCommand.cpp')
-rw-r--r-- | WebCore/editing/CompositeEditCommand.cpp | 177 |
1 files changed, 136 insertions, 41 deletions
diff --git a/WebCore/editing/CompositeEditCommand.cpp b/WebCore/editing/CompositeEditCommand.cpp index 0496a8f..1617be8 100644 --- a/WebCore/editing/CompositeEditCommand.cpp +++ b/WebCore/editing/CompositeEditCommand.cpp @@ -20,7 +20,7 @@ * 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. + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" @@ -71,7 +71,7 @@ namespace WebCore { using namespace HTMLNames; -CompositeEditCommand::CompositeEditCommand(Document *document) +CompositeEditCommand::CompositeEditCommand(Document *document) : EditCommand(document) { } @@ -396,7 +396,7 @@ void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position) Node* node = position.node(); if (!node || !node->isTextNode()) return; - Text* textNode = static_cast<Text*>(node); + Text* textNode = static_cast<Text*>(node); if (textNode->length() == 0) return; @@ -739,6 +739,129 @@ void CompositeEditCommand::pushPartiallySelectedAnchorElementsDown() setEndingSelection(originalSelection); } +// Clone the paragraph between start and end under blockElement, +// preserving the hierarchy up to outerNode. + +void CompositeEditCommand::cloneParagraphUnderNewElement(Position& start, Position& end, Node* outerNode, Element* blockElement) +{ + // First we clone the outerNode + + RefPtr<Node> lastNode = outerNode->cloneNode(isTableElement(outerNode)); + appendNode(lastNode, blockElement); + + if (start.node() != outerNode) { + Vector<RefPtr<Node> > ancestors; + + // Insert each node from innerNode to outerNode (excluded) in a list. + for (Node* n = start.node(); n && n != outerNode; n = n->parentNode()) + ancestors.append(n); + + // Clone every node between start.node() and outerBlock. + + for (size_t i = ancestors.size(); i != 0; --i) { + Node* item = ancestors[i - 1].get(); + RefPtr<Node> child = item->cloneNode(isTableElement(item)); + appendNode(child, static_cast<Element *>(lastNode.get())); + lastNode = child.release(); + } + } + + // Handle the case of paragraphs with more than one node, + // cloning all the siblings until end.node() is reached. + + if (start.node() != end.node()) { + for (Node* n = start.node()->nextSibling(); n != NULL; n = n->nextSibling()) { + RefPtr<Node> clonedNode = n->cloneNode(true); + insertNodeAfter(clonedNode, lastNode); + lastNode = clonedNode.release(); + if (n == end.node()) + break; + } + } +} + + +// There are bugs in deletion when it removes a fully selected table/list. +// It expands and removes the entire table/list, but will let content +// before and after the table/list collapse onto one line. +// Deleting a paragraph will leave a placeholder. Remove it (and prune +// empty or unrendered parents). + +void CompositeEditCommand::cleanupAfterDeletion() +{ + VisiblePosition caretAfterDelete = endingSelection().visibleStart(); + if (isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { + // Note: We want the rightmost candidate. + Position position = caretAfterDelete.deepEquivalent().downstream(); + Node* node = position.node(); + // Normally deletion will leave a br as a placeholder. + if (node->hasTagName(brTag)) + removeNodeAndPruneAncestors(node); + // If the selection to move was empty and in an empty block that + // doesn't require a placeholder to prop itself open (like a bordered + // div or an li), remove it during the move (the list removal code + // expects this behavior). + else if (isBlock(node)) + removeNodeAndPruneAncestors(node); + else if (lineBreakExistsAtPosition(position)) { + // There is a preserved '\n' at caretAfterDelete. + // We can safely assume this is a text node. + Text* textNode = static_cast<Text*>(node); + if (textNode->length() == 1) + removeNodeAndPruneAncestors(node); + else + deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1); + } + } +} + +// This is a version of moveParagraph that preserves style by keeping the original markup +// It is currently used only by IndentOutdentCommand but it is meant to be used in the +// future by several other commands such as InsertList and the align commands. +// The blockElement parameter is the element to move the paragraph to, +// outerNode is the top element of the paragraph hierarchy. + +void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode) +{ + ASSERT(outerNode); + ASSERT(blockElement); + + VisiblePosition beforeParagraph = startOfParagraphToMove.previous(); + VisiblePosition afterParagraph(endOfParagraphToMove.next()); + + // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move. + // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. + Position start = startOfParagraphToMove.deepEquivalent().downstream(); + Position end = endOfParagraphToMove.deepEquivalent().upstream(); + + cloneParagraphUnderNewElement(start, end, outerNode, blockElement); + + setEndingSelection(VisibleSelection(start, end, DOWNSTREAM)); + deleteSelection(false, false, false, false); + + // There are bugs in deletion when it removes a fully selected table/list. + // It expands and removes the entire table/list, but will let content + // before and after the table/list collapse onto one line. + + cleanupAfterDeletion(); + + // Add a br if pruning an empty block level element caused a collapse. For example: + // foo^ + // <div>bar</div> + // baz + // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would + // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br. + // Must recononicalize these two VisiblePositions after the pruning above. + beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); + afterParagraph = VisiblePosition(afterParagraph.deepEquivalent()); + + if (beforeParagraph.isNotNull() && !isTableElement(beforeParagraph.deepEquivalent().node()) && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) { + // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. + insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); + } +} + + // This moves a paragraph preserving its style. void CompositeEditCommand::moveParagraph(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle) { @@ -784,7 +907,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap VisiblePosition afterParagraph(endOfParagraphToMove.next()); // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move. - // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. + // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. Position start = startOfParagraphToMove.deepEquivalent().downstream(); Position end = endOfParagraphToMove.deepEquivalent().upstream(); @@ -793,7 +916,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap Position endRangeCompliant = rangeCompliantEquivalent(end); RefPtr<Range> range = Range::create(document(), startRangeCompliant.node(), startRangeCompliant.deprecatedEditingOffset(), endRangeCompliant.node(), endRangeCompliant.deprecatedEditingOffset()); - // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It + // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It // shouldn't matter though, since moved paragraphs will usually be quite small. RefPtr<DocumentFragment> fragment = startOfParagraphToMove != endOfParagraphToMove ? createFragmentFromMarkup(document(), createMarkup(range.get(), 0, DoNotAnnotateForInterchange, true), "") : 0; @@ -813,42 +936,14 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap deleteSelection(false, false, false, false); ASSERT(destination.deepEquivalent().node()->inDocument()); - - // There are bugs in deletion when it removes a fully selected table/list. - // It expands and removes the entire table/list, but will let content - // before and after the table/list collapse onto one line. - - // Deleting a paragraph will leave a placeholder. Remove it (and prune - // empty or unrendered parents). - VisiblePosition caretAfterDelete = endingSelection().visibleStart(); - if (isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { - // Note: We want the rightmost candidate. - Position position = caretAfterDelete.deepEquivalent().downstream(); - Node* node = position.node(); - // Normally deletion will leave a br as a placeholder. - if (node->hasTagName(brTag)) - removeNodeAndPruneAncestors(node); - // If the selection to move was empty and in an empty block that - // doesn't require a placeholder to prop itself open (like a bordered - // div or an li), remove it during the move (the list removal code - // expects this behavior). - else if (isBlock(node)) - removeNodeAndPruneAncestors(node); - else if (lineBreakExistsAtVisiblePosition(caretAfterDelete)) { - // There is a preserved '\n' at caretAfterDelete. - Text* textNode = static_cast<Text*>(node); - if (textNode->length() == 1) - removeNodeAndPruneAncestors(node); - else - deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1); - } - } - // Add a br if pruning an empty block level element caused a collapse. For example: + cleanupAfterDeletion(); + + // Add a br if pruning an empty block level element caused a collapse. For example: // foo^ // <div>bar</div> // baz - // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would + // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br. // Must recononicalize these two VisiblePositions after the pruning above. beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); @@ -912,7 +1007,7 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() removeNodePreservingChildren(listNode->parentNode()); newBlock = createListItemElement(document()); } - // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph. + // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph. } else if (blockEnclosingList->hasTagName(olTag) || blockEnclosingList->hasTagName(ulTag)) newBlock = createListItemElement(document()); } @@ -971,7 +1066,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() // to hold the caret before the highest blockquote. insertNodeBefore(br, highestBlockquote); VisiblePosition atBR(Position(br.get(), 0)); - // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert + // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert // a second one. if (!isStartOfParagraph(atBR)) insertNodeBefore(createBreakElement(document()), br); @@ -1002,7 +1097,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() return true; } -// Operations use this function to avoid inserting content into an anchor when at the start or the end of +// Operations use this function to avoid inserting content into an anchor when at the start or the end of // that anchor, as in NSTextView. // FIXME: This is only an approximation of NSTextViews insertion behavior, which varies depending on how // the caret was made. @@ -1022,7 +1117,7 @@ Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Posi if (enclosingAnchor && !isBlock(enclosingAnchor)) { VisiblePosition firstInAnchor(firstDeepEditingPositionForNode(enclosingAnchor)); VisiblePosition lastInAnchor(lastDeepEditingPositionForNode(enclosingAnchor)); - // If visually just after the anchor, insert *inside* the anchor unless it's the last + // If visually just after the anchor, insert *inside* the anchor unless it's the last // VisiblePosition in the document, to match NSTextView. if (visiblePos == lastInAnchor) { // Make sure anchors are pushed down before avoiding them so that we don't |