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
36namespace WebCore {
37
38using namespace HTMLNames;
39
40BreakBlockquoteCommand::BreakBlockquoteCommand(Document *document)
41    : CompositeEditCommand(document)
42{
43}
44
45void 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