Home | History | Annotate | Download | only in editing
      1 /*
      2  * Copyright (C) 2005 Apple Computer, 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 (INCLUDING, 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 "core/editing/BreakBlockquoteCommand.h"
     28 
     29 #include "core/HTMLNames.h"
     30 #include "core/dom/NodeTraversal.h"
     31 #include "core/dom/Text.h"
     32 #include "core/editing/VisiblePosition.h"
     33 #include "core/editing/htmlediting.h"
     34 #include "core/html/HTMLBRElement.h"
     35 #include "core/html/HTMLElement.h"
     36 #include "core/html/HTMLQuoteElement.h"
     37 #include "core/rendering/RenderListItem.h"
     38 
     39 namespace blink {
     40 
     41 using namespace HTMLNames;
     42 
     43 BreakBlockquoteCommand::BreakBlockquoteCommand(Document& document)
     44     : CompositeEditCommand(document)
     45 {
     46 }
     47 
     48 void BreakBlockquoteCommand::doApply()
     49 {
     50     if (endingSelection().isNone())
     51         return;
     52 
     53     // Delete the current selection.
     54     if (endingSelection().isRange())
     55         deleteSelection(false, false);
     56 
     57     // This is a scenario that should never happen, but we want to
     58     // make sure we don't dereference a null pointer below.
     59 
     60     ASSERT(!endingSelection().isNone());
     61 
     62     if (endingSelection().isNone())
     63         return;
     64 
     65     VisiblePosition visiblePos = endingSelection().visibleStart();
     66 
     67     // pos is a position equivalent to the caret.  We use downstream() so that pos will
     68     // be in the first node that we need to move (there are a few exceptions to this, see below).
     69     Position pos = endingSelection().start().downstream();
     70 
     71     // Find the top-most blockquote from the start.
     72     HTMLQuoteElement* topBlockquote = toHTMLQuoteElement(highestEnclosingNodeOfType(pos, isMailHTMLBlockquoteElement));
     73     if (!topBlockquote || !topBlockquote->parentNode())
     74         return;
     75 
     76     RefPtrWillBeRawPtr<HTMLBRElement> breakElement = createBreakElement(document());
     77 
     78     bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote);
     79 
     80     // If the position is at the beginning of the top quoted content, we don't need to break the quote.
     81     // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content.
     82     if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) {
     83         insertNodeBefore(breakElement.get(), topBlockquote);
     84         setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), DOWNSTREAM, endingSelection().isDirectional()));
     85         rebalanceWhitespace();
     86         return;
     87     }
     88 
     89     // Insert a break after the top blockquote.
     90     insertNodeAfter(breakElement.get(), topBlockquote);
     91 
     92     // If we're inserting the break at the end of the quoted content, we don't need to break the quote.
     93     if (isLastVisPosInNode) {
     94         setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), DOWNSTREAM, endingSelection().isDirectional()));
     95         rebalanceWhitespace();
     96         return;
     97     }
     98 
     99     // Don't move a line break just after the caret.  Doing so would create an extra, empty paragraph
    100     // in the new blockquote.
    101     if (lineBreakExistsAtVisiblePosition(visiblePos))
    102         pos = pos.next();
    103 
    104     // Adjust the position so we don't split at the beginning of a quote.
    105     while (isFirstVisiblePositionInNode(VisiblePosition(pos), toHTMLQuoteElement(enclosingNodeOfType(pos, isMailHTMLBlockquoteElement))))
    106         pos = pos.previous();
    107 
    108     // startNode is the first node that we need to move to the new blockquote.
    109     Node* startNode = pos.deprecatedNode();
    110     ASSERT(startNode);
    111 
    112     // Split at pos if in the middle of a text node.
    113     if (startNode->isTextNode()) {
    114         Text* textNode = toText(startNode);
    115         if ((unsigned)pos.deprecatedEditingOffset() >= textNode->length()) {
    116             startNode = NodeTraversal::next(*startNode);
    117             ASSERT(startNode);
    118         } else if (pos.deprecatedEditingOffset() > 0)
    119             splitTextNode(textNode, pos.deprecatedEditingOffset());
    120     } else if (pos.deprecatedEditingOffset() > 0) {
    121         Node* childAtOffset = NodeTraversal::childAt(*startNode, pos.deprecatedEditingOffset());
    122         startNode = childAtOffset ? childAtOffset : NodeTraversal::next(*startNode);
    123         ASSERT(startNode);
    124     }
    125 
    126     // If there's nothing inside topBlockquote to move, we're finished.
    127     if (!startNode->isDescendantOf(topBlockquote)) {
    128         setEndingSelection(VisibleSelection(VisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional()));
    129         return;
    130     }
    131 
    132     // Build up list of ancestors in between the start node and the top blockquote.
    133     WillBeHeapVector<RefPtrWillBeMember<Element> > ancestors;
    134     for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement())
    135         ancestors.append(node);
    136 
    137     // Insert a clone of the top blockquote after the break.
    138     RefPtrWillBeRawPtr<Element> clonedBlockquote = topBlockquote->cloneElementWithoutChildren();
    139     insertNodeAfter(clonedBlockquote.get(), breakElement.get());
    140 
    141     // Clone startNode's ancestors into the cloned blockquote.
    142     // On exiting this loop, clonedAncestor is the lowest ancestor
    143     // that was cloned (i.e. the clone of either ancestors.last()
    144     // or clonedBlockquote if ancestors is empty).
    145     RefPtrWillBeRawPtr<Element> clonedAncestor = clonedBlockquote;
    146     for (size_t i = ancestors.size(); i != 0; --i) {
    147         RefPtrWillBeRawPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren();
    148         // Preserve list item numbering in cloned lists.
    149         if (isHTMLOListElement(*clonedChild)) {
    150             Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode;
    151             // The first child of the cloned list might not be a list item element,
    152             // find the first one so that we know where to start numbering.
    153             while (listChildNode && !isHTMLLIElement(*listChildNode))
    154                 listChildNode = listChildNode->nextSibling();
    155             if (listChildNode && listChildNode->renderer() && listChildNode->renderer()->isListItem())
    156                 setNodeAttribute(clonedChild, startAttr, AtomicString::number(toRenderListItem(listChildNode->renderer())->value()));
    157         }
    158 
    159         appendNode(clonedChild.get(), clonedAncestor.get());
    160         clonedAncestor = clonedChild;
    161     }
    162 
    163     moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor);
    164 
    165     if (!ancestors.isEmpty()) {
    166         // Split the tree up the ancestor chain until the topBlockquote
    167         // Throughout this loop, clonedParent is the clone of ancestor's parent.
    168         // This is so we can clone ancestor's siblings and place the clones
    169         // into the clone corresponding to the ancestor's parent.
    170         RefPtrWillBeRawPtr<Element> ancestor = nullptr;
    171         RefPtrWillBeRawPtr<Element> clonedParent = nullptr;
    172         for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement();
    173              ancestor && ancestor != topBlockquote;
    174              ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement())
    175             moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent);
    176 
    177         // If the startNode's original parent is now empty, remove it
    178         Element* originalParent = ancestors.first().get();
    179         if (!originalParent->hasChildren())
    180             removeNode(originalParent);
    181     }
    182 
    183     // Make sure the cloned block quote renders.
    184     addBlockPlaceholderIfNeeded(clonedBlockquote.get());
    185 
    186     // Put the selection right before the break.
    187     setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), DOWNSTREAM, endingSelection().isDirectional()));
    188     rebalanceWhitespace();
    189 }
    190 
    191 } // namespace blink
    192