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