IndentOutdentCommand.cpp [plain text]
#include "config.h"
#include "IndentOutdentCommand.h"
#include "Document.h"
#include "Element.h"
#include "HTMLBlockquoteElement.h"
#include "HTMLNames.h"
#include "InsertLineBreakCommand.h"
#include "InsertListCommand.h"
#include "Range.h"
#include "SplitElementCommand.h"
#include "TextIterator.h"
#include "htmlediting.h"
#include "visible_units.h"
#include <wtf/StdLibExtras.h>
namespace WebCore {
using namespace HTMLNames;
static String indentBlockquoteString()
{
DEFINE_STATIC_LOCAL(String, string, ("webkit-indent-blockquote"));
return string;
}
static PassRefPtr<HTMLBlockquoteElement> createIndentBlockquoteElement(Document* document)
{
RefPtr<HTMLBlockquoteElement> element = new HTMLBlockquoteElement(blockquoteTag, document);
element->setAttribute(classAttr, indentBlockquoteString());
element->setAttribute(styleAttr, "margin: 0 0 0 40px; border: none; padding: 0px;");
return element.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)
{
}
PassRefPtr<Element> IndentOutdentCommand::prepareBlockquoteLevelForInsertion(const VisiblePosition& currentParagraph, RefPtr<Element>& lastBlockquote)
{
int currentBlockquoteLevel = 0;
int lastBlockquoteLevel = 0;
Node* node = currentParagraph.deepEquivalent().node();
while ((node = enclosingNodeOfType(Position(node->parentNode(), 0), &isIndentBlockquote)))
currentBlockquoteLevel++;
node = lastBlockquote.get();
while ((node = enclosingNodeOfType(Position(node->parentNode(), 0), &isIndentBlockquote)))
lastBlockquoteLevel++;
while (currentBlockquoteLevel > lastBlockquoteLevel) {
RefPtr<Element> newBlockquote = createIndentBlockquoteElement(document());
appendNode(newBlockquote, lastBlockquote);
lastBlockquote = newBlockquote;
lastBlockquoteLevel++;
}
while (currentBlockquoteLevel < lastBlockquoteLevel) {
lastBlockquote = static_cast<Element*>(enclosingNodeOfType(Position(lastBlockquote->parentNode(), 0), isIndentBlockquote));
lastBlockquoteLevel--;
}
RefPtr<Element> placeholder = createBreakElement(document());
appendNode(placeholder, lastBlockquote);
VisiblePosition visiblePos(Position(placeholder.get(), 0));
if (!isStartOfParagraph(visiblePos))
insertNodeBefore(createBreakElement(document()), placeholder);
return placeholder.release();
}
bool IndentOutdentCommand::tryIndentingAsListItem(const VisiblePosition& endOfCurrentParagraph)
{
Node* lastNodeInSelectedParagraph = endOfCurrentParagraph.deepEquivalent().node();
RefPtr<Element> listNode = enclosingList(lastNodeInSelectedParagraph);
if (!listNode)
return false;
HTMLElement* selectedListItem = enclosingListChild(lastNodeInSelectedParagraph);
Element* previousList = selectedListItem->previousElementSibling();
Element* nextList = selectedListItem->nextElementSibling();
RefPtr<Element> newList = document()->createElement(listNode->tagQName(), false);
RefPtr<Element> newListItem = selectedListItem->cloneElementWithoutChildren();
RefPtr<Element> placeholder = createBreakElement(document());
insertNodeBefore(newList, selectedListItem);
appendNode(newListItem, newList);
appendNode(placeholder, newListItem);
moveParagraph(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, VisiblePosition(Position(placeholder, 0)), true);
if (canMergeLists(previousList, newList.get()))
mergeIdenticalElements(previousList, newList);
if (canMergeLists(newList.get(), nextList))
mergeIdenticalElements(newList, nextList);
return true;
}
void IndentOutdentCommand::indentIntoBlockquote(const VisiblePosition& endOfCurrentParagraph, const VisiblePosition& endOfNextParagraph, RefPtr<Element>& targetBlockquote)
{
Node* enclosingCell = 0;
if (!targetBlockquote) {
targetBlockquote = createIndentBlockquoteElement(document());
Position start = startOfParagraph(endOfCurrentParagraph).deepEquivalent();
enclosingCell = enclosingNodeOfType(start, &isTableCell);
Node* nodeToSplitTo = enclosingCell ? enclosingCell : editableRootForPosition(start);
RefPtr<Node> startOfNewBlock = splitTreeToNode(start.node(), nodeToSplitTo);
insertNodeBefore(targetBlockquote, startOfNewBlock);
}
RefPtr<Element> insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, targetBlockquote);
if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell))
targetBlockquote = 0;
moveParagraph(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, VisiblePosition(Position(insertionPoint, 0)), true);
}
bool IndentOutdentCommand::isAtUnsplittableElement(const Position& pos) const
{
Node* node = pos.node();
return node == editableRootForPosition(pos) || node == enclosingNodeOfType(pos, &isTableCell);
}
void IndentOutdentCommand::indentRegion()
{
VisibleSelection 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());
Position start = startOfSelection.deepEquivalent().downstream();
if (isAtUnsplittableElement(start)) {
RefPtr<Element> blockquote = createIndentBlockquoteElement(document());
insertNodeAt(blockquote, start);
RefPtr<Element> placeholder = createBreakElement(document());
appendNode(placeholder, blockquote);
setEndingSelection(VisibleSelection(Position(placeholder.get(), 0), DOWNSTREAM));
return;
}
RefPtr<Element> blockquoteForNextIndent;
VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next());
while (endOfCurrentParagraph != endAfterSelection) {
VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
if (tryIndentingAsListItem(endOfCurrentParagraph))
blockquoteForNextIndent = 0;
else
indentIntoBlockquote(endOfCurrentParagraph, endOfNextParagraph, blockquoteForNextIndent);
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(VisibleSelection(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 || !isContentEditable(enclosingNode->parentNode())) return;
if (enclosingNode->hasTagName(olTag)) {
applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::OrderedList));
return;
}
if (enclosingNode->hasTagName(ulTag)) {
applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::UnorderedList));
return;
}
VisiblePosition positionInEnclosingBlock = VisiblePosition(Position(enclosingNode, 0));
VisiblePosition startOfEnclosingBlock = startOfBlock(positionInEnclosingBlock);
VisiblePosition lastPositionInEnclosingBlock = VisiblePosition(Position(enclosingNode, enclosingNode->childNodeCount()));
VisiblePosition endOfEnclosingBlock = endOfBlock(lastPositionInEnclosingBlock);
if (visibleStartOfParagraph == startOfEnclosingBlock &&
visibleEndOfParagraph == endOfEnclosingBlock) {
Node* splitPoint = enclosingNode->nextSibling();
removeNodePreservingChildren(enclosingNode);
if (splitPoint) {
if (Node* splitPointParent = splitPoint->parentNode()) {
if (isIndentBlockquote(splitPointParent)
&& !isIndentBlockquote(splitPoint)
&& isContentEditable(splitPointParent->parentNode())) splitElement(static_cast<Element*>(splitPointParent), splitPoint);
}
}
updateLayout();
visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEquivalent());
visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquivalent());
if (visibleStartOfParagraph.isNotNull() && !isStartOfParagraph(visibleStartOfParagraph))
insertNodeAt(createBreakElement(document()), visibleStartOfParagraph.deepEquivalent());
if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfParagraph))
insertNodeAt(createBreakElement(document()), 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, splitBlockquoteNode);
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(VisibleSelection(originalSelectionEnd, DOWNSTREAM));
else
setEndingSelection(endOfCurrentParagraph);
outdentParagraph();
endOfCurrentParagraph = endOfNextParagraph;
}
setEndingSelection(VisibleSelection(originalSelectionStart, endingSelection().end(), DOWNSTREAM));
}
void IndentOutdentCommand::doApply()
{
if (endingSelection().isNone())
return;
if (!endingSelection().rootEditableElement())
return;
VisiblePosition visibleEnd = endingSelection().visibleEnd();
VisiblePosition visibleStart = endingSelection().visibleStart();
if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd))
setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true)));
if (m_typeOfAction == Indent)
indentRegion();
else
outdentRegion();
}
}