diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:52 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:52 -0800 |
commit | 8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2 (patch) | |
tree | 11425ea0b299d6fb89c6d3618a22d97d5bf68d0f /WebCore/editing/IndentOutdentCommand.cpp | |
parent | 648161bb0edfc3d43db63caed5cc5213bc6cb78f (diff) | |
download | external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.zip external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.gz external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'WebCore/editing/IndentOutdentCommand.cpp')
-rw-r--r-- | WebCore/editing/IndentOutdentCommand.cpp | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/WebCore/editing/IndentOutdentCommand.cpp b/WebCore/editing/IndentOutdentCommand.cpp new file mode 100644 index 0000000..385f1b7 --- /dev/null +++ b/WebCore/editing/IndentOutdentCommand.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2006, 2008 Apple 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 (IndentOutdentCommandINCLUDING, 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 "config.h" +#include "Element.h" +#include "IndentOutdentCommand.h" +#include "InsertListCommand.h" +#include "Document.h" +#include "htmlediting.h" +#include "HTMLElement.h" +#include "HTMLNames.h" +#include "InsertLineBreakCommand.h" +#include "Range.h" +#include "SplitElementCommand.h" +#include "TextIterator.h" +#include "visible_units.h" + +namespace WebCore { + +using namespace HTMLNames; + +static String indentBlockquoteString() +{ + static String string = "webkit-indent-blockquote"; + return string; +} + +static PassRefPtr<Element> createIndentBlockquoteElement(Document* document) +{ + RefPtr<Element> indentBlockquoteElement = createElement(document, "blockquote"); + indentBlockquoteElement->setAttribute(classAttr, indentBlockquoteString()); + indentBlockquoteElement->setAttribute(styleAttr, "margin: 0 0 0 40px; border: none; padding: 0px;"); + return indentBlockquoteElement.release(); +} + +static bool isIndentBlockquote(const Node* node) +{ + if (!node || !node->hasTagName(blockquoteTag) || !node->isElementNode()) + return false; + + const Element* elem = static_cast<const Element*>(node); + return elem->getAttribute(classAttr) == indentBlockquoteString(); +} + +static bool isListOrIndentBlockquote(const Node* node) +{ + return node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || isIndentBlockquote(node)); +} + +IndentOutdentCommand::IndentOutdentCommand(Document* document, EIndentType typeOfAction, int marginInPixels) + : CompositeEditCommand(document), m_typeOfAction(typeOfAction), m_marginInPixels(marginInPixels) +{} + +// This function is a workaround for moveParagraph's tendency to strip blockquotes. It updates lastBlockquote to point to the +// correct level for the current paragraph, and returns a pointer to a placeholder br where the insertion should be performed. +Node* IndentOutdentCommand::prepareBlockquoteLevelForInsertion(VisiblePosition& currentParagraph, Node** lastBlockquote) +{ + int currentBlockquoteLevel = 0; + int lastBlockquoteLevel = 0; + Node* node = currentParagraph.deepEquivalent().node(); + while ((node = enclosingNodeOfType(Position(node->parentNode(), 0), &isIndentBlockquote))) + currentBlockquoteLevel++; + node = *lastBlockquote; + while ((node = enclosingNodeOfType(Position(node->parentNode(), 0), &isIndentBlockquote))) + lastBlockquoteLevel++; + while (currentBlockquoteLevel > lastBlockquoteLevel) { + RefPtr<Node> newBlockquote = createIndentBlockquoteElement(document()); + appendNode(newBlockquote.get(), *lastBlockquote); + *lastBlockquote = newBlockquote.get(); + lastBlockquoteLevel++; + } + while (currentBlockquoteLevel < lastBlockquoteLevel) { + *lastBlockquote = enclosingNodeOfType(Position((*lastBlockquote)->parentNode(), 0), &isIndentBlockquote); + lastBlockquoteLevel--; + } + RefPtr<Node> placeholder = createBreakElement(document()); + appendNode(placeholder.get(), *lastBlockquote); + // Add another br before the placeholder if it collapsed. + VisiblePosition visiblePos(Position(placeholder.get(), 0)); + if (!isStartOfParagraph(visiblePos)) + insertNodeBefore(createBreakElement(document()).get(), placeholder.get()); + return placeholder.get(); +} + +void IndentOutdentCommand::indentRegion() +{ + Selection selection = selectionForParagraphIteration(endingSelection()); + VisiblePosition startOfSelection = selection.visibleStart(); + VisiblePosition endOfSelection = selection.visibleEnd(); + int startIndex = indexForVisiblePosition(startOfSelection); + int endIndex = indexForVisiblePosition(endOfSelection); + + ASSERT(!startOfSelection.isNull()); + ASSERT(!endOfSelection.isNull()); + + // Special case empty root editable elements because there's nothing to split + // and there's nothing to move. + Position start = startOfSelection.deepEquivalent().downstream(); + if (start.node() == editableRootForPosition(start)) { + RefPtr<Node> blockquote = createIndentBlockquoteElement(document()); + insertNodeAt(blockquote.get(), start); + RefPtr<Node> placeholder = createBreakElement(document()); + appendNode(placeholder.get(), blockquote.get()); + setEndingSelection(Selection(Position(placeholder.get(), 0), DOWNSTREAM)); + return; + } + + Node* previousListNode = 0; + Node* newListNode = 0; + Node* newBlockquote = 0; + VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); + VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next()); + while (endOfCurrentParagraph != endAfterSelection) { + // Iterate across the selected paragraphs... + VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); + Node* listNode = enclosingList(endOfCurrentParagraph.deepEquivalent().node()); + Node* insertionPoint; + if (listNode) { + RefPtr<Node> placeholder = createBreakElement(document()); + insertionPoint = placeholder.get(); + newBlockquote = 0; + RefPtr<Node> listItem = createListItemElement(document()); + if (listNode == previousListNode) { + // The previous paragraph was inside the same list, so add this list item to the list we already created + appendNode(listItem.get(), newListNode); + appendNode(placeholder.get(), listItem.get()); + } else { + // Clone the list element, insert it before the current paragraph, and move the paragraph into it. + RefPtr<Node> clonedList = static_cast<Element*>(listNode)->cloneNode(false); + insertNodeBefore(clonedList.get(), enclosingListChild(endOfCurrentParagraph.deepEquivalent().node())); + appendNode(listItem.get(), clonedList.get()); + appendNode(placeholder.get(), listItem.get()); + newListNode = clonedList.get(); + previousListNode = listNode; + } + } else if (newBlockquote) + // The previous paragraph was put into a new blockquote, so move this paragraph there as well + insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, &newBlockquote); + else { + // Create a new blockquote and insert it as a child of the root editable element. We accomplish + // this by splitting all parents of the current paragraph up to that point. + RefPtr<Node> blockquote = createIndentBlockquoteElement(document()); + Position start = startOfParagraph(endOfCurrentParagraph).deepEquivalent(); + + Node* enclosingCell = enclosingNodeOfType(start, &isTableCell); + Node* nodeToSplitTo = enclosingCell ? enclosingCell : editableRootForPosition(start); + RefPtr<Node> startOfNewBlock = splitTreeToNode(start.node(), nodeToSplitTo); + insertNodeBefore(blockquote.get(), startOfNewBlock.get()); + newBlockquote = blockquote.get(); + insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, &newBlockquote); + // Don't put the next paragraph in the blockquote we just created for this paragraph unless + // the next paragraph is in the same cell. + if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell)) + newBlockquote = 0; + } + moveParagraph(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, VisiblePosition(Position(insertionPoint, 0)), true); + // moveParagraph should not destroy content that contains endOfNextParagraph, but if it does, return here + // to avoid a crash. + if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().node()->inDocument()) { + ASSERT_NOT_REACHED(); + return; + } + endOfCurrentParagraph = endOfNextParagraph; + } + + RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true); + RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true); + if (startRange && endRange) + setEndingSelection(Selection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM)); +} + +void IndentOutdentCommand::outdentParagraph() +{ + VisiblePosition visibleStartOfParagraph = startOfParagraph(endingSelection().visibleStart()); + VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph); + + Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote); + if (!enclosingNode) + return; + + // Use InsertListCommand to remove the selection from the list + if (enclosingNode->hasTagName(olTag)) { + applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::OrderedList, "")); + return; + } + if (enclosingNode->hasTagName(ulTag)) { + applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::UnorderedList, "")); + return; + } + + // The selection is inside a blockquote + VisiblePosition positionInEnclosingBlock = VisiblePosition(Position(enclosingNode, 0)); + VisiblePosition startOfEnclosingBlock = startOfBlock(positionInEnclosingBlock); + VisiblePosition endOfEnclosingBlock = endOfBlock(positionInEnclosingBlock); + if (visibleStartOfParagraph == startOfEnclosingBlock && + visibleEndOfParagraph == endOfEnclosingBlock) { + // The blockquote doesn't contain anything outside the paragraph, so it can be totally removed. + removeNodePreservingChildren(enclosingNode); + updateLayout(); + visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEquivalent()); + visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquivalent()); + if (visibleStartOfParagraph.isNotNull() && !isStartOfParagraph(visibleStartOfParagraph)) + insertNodeAt(createBreakElement(document()).get(), visibleStartOfParagraph.deepEquivalent()); + if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfParagraph)) + insertNodeAt(createBreakElement(document()).get(), visibleEndOfParagraph.deepEquivalent()); + return; + } + Node* enclosingBlockFlow = enclosingBlockFlowElement(visibleStartOfParagraph); + RefPtr<Node> splitBlockquoteNode = enclosingNode; + if (enclosingBlockFlow != enclosingNode) + splitBlockquoteNode = splitTreeToNode(enclosingBlockFlowElement(visibleStartOfParagraph), enclosingNode, true); + RefPtr<Node> placeholder = createBreakElement(document()); + insertNodeBefore(placeholder.get(), splitBlockquoteNode.get()); + moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), VisiblePosition(Position(placeholder.get(), 0)), true); +} + +void IndentOutdentCommand::outdentRegion() +{ + VisiblePosition startOfSelection = endingSelection().visibleStart(); + VisiblePosition endOfSelection = endingSelection().visibleEnd(); + VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection); + + ASSERT(!startOfSelection.isNull()); + ASSERT(!endOfSelection.isNull()); + + if (endOfParagraph(startOfSelection) == endOfLastParagraph) { + outdentParagraph(); + return; + } + + Position originalSelectionEnd = endingSelection().end(); + setEndingSelection(endingSelection().visibleStart()); + outdentParagraph(); + Position originalSelectionStart = endingSelection().start(); + VisiblePosition endOfCurrentParagraph = endOfParagraph(endOfParagraph(endingSelection().visibleStart()).next(true)); + VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next()); + while (endOfCurrentParagraph != endAfterSelection) { + VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); + if (endOfCurrentParagraph == endOfLastParagraph) + setEndingSelection(Selection(originalSelectionEnd, DOWNSTREAM)); + else + setEndingSelection(endOfCurrentParagraph); + outdentParagraph(); + endOfCurrentParagraph = endOfNextParagraph; + } + setEndingSelection(Selection(originalSelectionStart, endingSelection().end(), DOWNSTREAM)); +} + +void IndentOutdentCommand::doApply() +{ + if (endingSelection().isNone()) + return; + + if (!endingSelection().rootEditableElement()) + return; + + VisiblePosition visibleEnd = endingSelection().visibleEnd(); + VisiblePosition visibleStart = endingSelection().visibleStart(); + // When a selection ends at the start of a paragraph, we rarely paint + // the selection gap before that paragraph, because there often is no gap. + // In a case like this, it's not obvious to the user that the selection + // ends "inside" that paragraph, so it would be confusing if Indent/Outdent + // operated on that paragraph. + // FIXME: We paint the gap before some paragraphs that are indented with left + // margin/padding, but not others. We should make the gap painting more consistent and + // then use a left margin/padding rule here. + if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) + setEndingSelection(Selection(visibleStart, visibleEnd.previous(true))); + + if (m_typeOfAction == Indent) + indentRegion(); + else + outdentRegion(); +} + +} |