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