Home | History | Annotate | Download | only in editing
      1 /*
      2  * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (IndentOutdentCommandINCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "IndentOutdentCommand.h"
     28 
     29 #include "Document.h"
     30 #include "Element.h"
     31 #include "HTMLBlockquoteElement.h"
     32 #include "HTMLNames.h"
     33 #include "InsertLineBreakCommand.h"
     34 #include "InsertListCommand.h"
     35 #include "Range.h"
     36 #include "SplitElementCommand.h"
     37 #include "Text.h"
     38 #include "TextIterator.h"
     39 #include "htmlediting.h"
     40 #include "visible_units.h"
     41 #include <wtf/StdLibExtras.h>
     42 
     43 namespace WebCore {
     44 
     45 using namespace HTMLNames;
     46 
     47 static bool isListOrIndentBlockquote(const Node* node)
     48 {
     49     return node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(blockquoteTag));
     50 }
     51 
     52 IndentOutdentCommand::IndentOutdentCommand(Document* document, EIndentType typeOfAction, int marginInPixels)
     53     : ApplyBlockElementCommand(document, blockquoteTag, "webkit-indent-blockquote", "margin: 0 0 0 40px; border: none; padding: 0px;")
     54     , m_typeOfAction(typeOfAction)
     55     , m_marginInPixels(marginInPixels)
     56 {
     57 }
     58 
     59 bool IndentOutdentCommand::tryIndentingAsListItem(const Position& start, const Position& end)
     60 {
     61     // If our selection is not inside a list, bail out.
     62     Node* lastNodeInSelectedParagraph = start.deprecatedNode();
     63     RefPtr<Element> listNode = enclosingList(lastNodeInSelectedParagraph);
     64     if (!listNode)
     65         return false;
     66 
     67     // Find the block that we want to indent.  If it's not a list item (e.g., a div inside a list item), we bail out.
     68     Element* selectedListItem = static_cast<Element*>(enclosingBlock(lastNodeInSelectedParagraph));
     69 
     70     // FIXME: we need to deal with the case where there is no li (malformed HTML)
     71     if (!selectedListItem->hasTagName(liTag))
     72         return false;
     73 
     74     // FIXME: previousElementSibling does not ignore non-rendered content like <span></span>.  Should we?
     75     Element* previousList = selectedListItem->previousElementSibling();
     76     Element* nextList = selectedListItem->nextElementSibling();
     77 
     78     RefPtr<Element> newList = document()->createElement(listNode->tagQName(), false);
     79     insertNodeBefore(newList, selectedListItem);
     80 
     81     moveParagraphWithClones(start, end, newList.get(), selectedListItem);
     82 
     83     if (canMergeLists(previousList, newList.get()))
     84         mergeIdenticalElements(previousList, newList);
     85     if (canMergeLists(newList.get(), nextList))
     86         mergeIdenticalElements(newList, nextList);
     87 
     88     return true;
     89 }
     90 
     91 void IndentOutdentCommand::indentIntoBlockquote(const Position& start, const Position& end, RefPtr<Element>& targetBlockquote)
     92 {
     93     Node* enclosingCell = enclosingNodeOfType(start, &isTableCell);
     94     Node* nodeToSplitTo;
     95     if (enclosingCell)
     96         nodeToSplitTo = enclosingCell;
     97     else if (enclosingList(start.deprecatedNode()))
     98         nodeToSplitTo = enclosingBlock(start.deprecatedNode());
     99     else
    100         nodeToSplitTo = editableRootForPosition(start);
    101 
    102     if (!nodeToSplitTo)
    103         return;
    104 
    105     RefPtr<Node> outerBlock = (start.deprecatedNode() == nodeToSplitTo) ? start.deprecatedNode() : splitTreeToNode(start.deprecatedNode(), nodeToSplitTo);
    106 
    107     if (!targetBlockquote) {
    108         // Create a new blockquote and insert it as a child of the root editable element. We accomplish
    109         // this by splitting all parents of the current paragraph up to that point.
    110         targetBlockquote = createBlockElement();
    111         insertNodeBefore(targetBlockquote, outerBlock);
    112     }
    113 
    114     moveParagraphWithClones(start, end, targetBlockquote.get(), outerBlock.get());
    115 }
    116 
    117 void IndentOutdentCommand::outdentParagraph()
    118 {
    119     VisiblePosition visibleStartOfParagraph = startOfParagraph(endingSelection().visibleStart());
    120     VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph);
    121 
    122     Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote);
    123     if (!enclosingNode || !enclosingNode->parentNode()->rendererIsEditable()) // We can't outdent if there is no place to go!
    124         return;
    125 
    126     // Use InsertListCommand to remove the selection from the list
    127     if (enclosingNode->hasTagName(olTag)) {
    128         applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::OrderedList));
    129         return;
    130     }
    131     if (enclosingNode->hasTagName(ulTag)) {
    132         applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::UnorderedList));
    133         return;
    134     }
    135 
    136     // The selection is inside a blockquote i.e. enclosingNode is a blockquote
    137     VisiblePosition positionInEnclosingBlock = VisiblePosition(firstPositionInNode(enclosingNode));
    138     // If the blockquote is inline, the start of the enclosing block coincides with
    139     // positionInEnclosingBlock.
    140     VisiblePosition startOfEnclosingBlock = (enclosingNode->renderer() && enclosingNode->renderer()->isInline()) ? positionInEnclosingBlock : startOfBlock(positionInEnclosingBlock);
    141     VisiblePosition lastPositionInEnclosingBlock = VisiblePosition(lastPositionInNode(enclosingNode));
    142     VisiblePosition endOfEnclosingBlock = endOfBlock(lastPositionInEnclosingBlock);
    143     if (visibleStartOfParagraph == startOfEnclosingBlock &&
    144         visibleEndOfParagraph == endOfEnclosingBlock) {
    145         // The blockquote doesn't contain anything outside the paragraph, so it can be totally removed.
    146         Node* splitPoint = enclosingNode->nextSibling();
    147         removeNodePreservingChildren(enclosingNode);
    148         // outdentRegion() assumes it is operating on the first paragraph of an enclosing blockquote, but if there are multiply nested blockquotes and we've
    149         // just removed one, then this assumption isn't true. By splitting the next containing blockquote after this node, we keep this assumption true
    150         if (splitPoint) {
    151             if (ContainerNode* splitPointParent = splitPoint->parentNode()) {
    152                 if (splitPointParent->hasTagName(blockquoteTag)
    153                     && !splitPoint->hasTagName(blockquoteTag)
    154                     && splitPointParent->parentNode()->rendererIsEditable()) // We can't outdent if there is no place to go!
    155                     splitElement(static_cast<Element*>(splitPointParent), splitPoint);
    156             }
    157         }
    158 
    159         updateLayout();
    160         visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEquivalent());
    161         visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquivalent());
    162         if (visibleStartOfParagraph.isNotNull() && !isStartOfParagraph(visibleStartOfParagraph))
    163             insertNodeAt(createBreakElement(document()), visibleStartOfParagraph.deepEquivalent());
    164         if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfParagraph))
    165             insertNodeAt(createBreakElement(document()), visibleEndOfParagraph.deepEquivalent());
    166 
    167         return;
    168     }
    169     Node* enclosingBlockFlow = enclosingBlock(visibleStartOfParagraph.deepEquivalent().deprecatedNode());
    170     RefPtr<Node> splitBlockquoteNode = enclosingNode;
    171     if (enclosingBlockFlow != enclosingNode)
    172         splitBlockquoteNode = splitTreeToNode(enclosingBlockFlow, enclosingNode, true);
    173     else {
    174         // We split the blockquote at where we start outdenting.
    175         splitElement(static_cast<Element*>(enclosingNode), visibleStartOfParagraph.deepEquivalent().deprecatedNode());
    176     }
    177     RefPtr<Node> placeholder = createBreakElement(document());
    178     insertNodeBefore(placeholder, splitBlockquoteNode);
    179     moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), positionBeforeNode(placeholder.get()), true);
    180 }
    181 
    182 // FIXME: We should merge this function with ApplyBlockElementCommand::formatSelection
    183 void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection)
    184 {
    185     VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection);
    186 
    187     if (endOfParagraph(startOfSelection) == endOfLastParagraph) {
    188         outdentParagraph();
    189         return;
    190     }
    191 
    192     Position originalSelectionEnd = endingSelection().end();
    193     VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
    194     VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next());
    195 
    196     while (endOfCurrentParagraph != endAfterSelection) {
    197         VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
    198         if (endOfCurrentParagraph == endOfLastParagraph)
    199             setEndingSelection(VisibleSelection(originalSelectionEnd, DOWNSTREAM));
    200         else
    201             setEndingSelection(endOfCurrentParagraph);
    202 
    203         outdentParagraph();
    204 
    205         // outdentParagraph could move more than one paragraph if the paragraph
    206         // is in a list item. As a result, endAfterSelection and endOfNextParagraph
    207         // could refer to positions no longer in the document.
    208         if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().anchorNode()->inDocument())
    209             break;
    210 
    211         if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().anchorNode()->inDocument()) {
    212             endOfCurrentParagraph = endingSelection().end();
    213             endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
    214         }
    215         endOfCurrentParagraph = endOfNextParagraph;
    216     }
    217 }
    218 
    219 void IndentOutdentCommand::formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection)
    220 {
    221     if (m_typeOfAction == Indent)
    222         ApplyBlockElementCommand::formatSelection(startOfSelection, endOfSelection);
    223     else
    224         outdentRegion(startOfSelection, endOfSelection);
    225 }
    226 
    227 void IndentOutdentCommand::formatRange(const Position& start, const Position& end, const Position&, RefPtr<Element>& blockquoteForNextIndent)
    228 {
    229     if (tryIndentingAsListItem(start, end))
    230         blockquoteForNextIndent = 0;
    231     else
    232         indentIntoBlockquote(start, end, blockquoteForNextIndent);
    233 }
    234 
    235 }
    236