OLD | NEW |
| (Empty) |
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/EditingUtilities.h" | |
33 #include "core/editing/VisiblePosition.h" | |
34 #include "core/html/HTMLBRElement.h" | |
35 #include "core/html/HTMLElement.h" | |
36 #include "core/html/HTMLQuoteElement.h" | |
37 #include "core/layout/LayoutListItem.h" | |
38 | |
39 namespace blink { | |
40 | |
41 using namespace HTMLNames; | |
42 | |
43 namespace { | |
44 | |
45 bool isFirstVisiblePositionInNode(const VisiblePosition& visiblePosition, const
ContainerNode* node) | |
46 { | |
47 if (visiblePosition.isNull()) | |
48 return false; | |
49 | |
50 if (!visiblePosition.deepEquivalent().computeContainerNode()->isDescendantOf
(node)) | |
51 return false; | |
52 | |
53 VisiblePosition previous = visiblePosition.previous(); | |
54 return previous.isNull() || !previous.deepEquivalent().anchorNode()->isDesce
ndantOf(node); | |
55 } | |
56 | |
57 bool isLastVisiblePositionInNode(const VisiblePosition& visiblePosition, const C
ontainerNode* node) | |
58 { | |
59 if (visiblePosition.isNull()) | |
60 return false; | |
61 | |
62 if (!visiblePosition.deepEquivalent().computeContainerNode()->isDescendantOf
(node)) | |
63 return false; | |
64 | |
65 VisiblePosition next = visiblePosition.next(); | |
66 return next.isNull() || !next.deepEquivalent().anchorNode()->isDescendantOf(
node); | |
67 } | |
68 | |
69 } // namespace | |
70 | |
71 BreakBlockquoteCommand::BreakBlockquoteCommand(Document& document) | |
72 : CompositeEditCommand(document) | |
73 { | |
74 } | |
75 | |
76 void BreakBlockquoteCommand::doApply() | |
77 { | |
78 if (endingSelection().isNone()) | |
79 return; | |
80 | |
81 // Delete the current selection. | |
82 if (endingSelection().isRange()) | |
83 deleteSelection(false, false); | |
84 | |
85 // This is a scenario that should never happen, but we want to | |
86 // make sure we don't dereference a null pointer below. | |
87 | |
88 ASSERT(!endingSelection().isNone()); | |
89 | |
90 if (endingSelection().isNone()) | |
91 return; | |
92 | |
93 VisiblePosition visiblePos = endingSelection().visibleStart(); | |
94 | |
95 // pos is a position equivalent to the caret. We use downstream() so that p
os will | |
96 // be in the first node that we need to move (there are a few exceptions to
this, see below). | |
97 Position pos = endingSelection().start().downstream(); | |
98 | |
99 // Find the top-most blockquote from the start. | |
100 HTMLQuoteElement* topBlockquote = toHTMLQuoteElement(highestEnclosingNodeOfT
ype(pos, isMailHTMLBlockquoteElement)); | |
101 if (!topBlockquote || !topBlockquote->parentNode()) | |
102 return; | |
103 | |
104 RefPtrWillBeRawPtr<HTMLBRElement> breakElement = createBreakElement(document
()); | |
105 | |
106 bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockqu
ote); | |
107 | |
108 // If the position is at the beginning of the top quoted content, we don't n
eed to break the quote. | |
109 // Instead, insert the break before the blockquote, unless the position is a
s the end of the the quoted content. | |
110 if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPos
InNode) { | |
111 insertNodeBefore(breakElement.get(), topBlockquote); | |
112 setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get(
)), DOWNSTREAM, endingSelection().isDirectional())); | |
113 rebalanceWhitespace(); | |
114 return; | |
115 } | |
116 | |
117 // Insert a break after the top blockquote. | |
118 insertNodeAfter(breakElement.get(), topBlockquote); | |
119 | |
120 // If we're inserting the break at the end of the quoted content, we don't n
eed to break the quote. | |
121 if (isLastVisPosInNode) { | |
122 setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get(
)), DOWNSTREAM, endingSelection().isDirectional())); | |
123 rebalanceWhitespace(); | |
124 return; | |
125 } | |
126 | |
127 // Don't move a line break just after the caret. Doing so would create an e
xtra, empty paragraph | |
128 // in the new blockquote. | |
129 if (lineBreakExistsAtVisiblePosition(visiblePos)) | |
130 pos = pos.next(); | |
131 | |
132 // Adjust the position so we don't split at the beginning of a quote. | |
133 while (isFirstVisiblePositionInNode(VisiblePosition(pos), toHTMLQuoteElement
(enclosingNodeOfType(pos, isMailHTMLBlockquoteElement)))) | |
134 pos = pos.previous(); | |
135 | |
136 // startNode is the first node that we need to move to the new blockquote. | |
137 Node* startNode = pos.anchorNode(); | |
138 ASSERT(startNode); | |
139 | |
140 // Split at pos if in the middle of a text node. | |
141 if (startNode->isTextNode()) { | |
142 Text* textNode = toText(startNode); | |
143 int textOffset = pos.computeOffsetInContainerNode(); | |
144 if ((unsigned)textOffset >= textNode->length()) { | |
145 startNode = NodeTraversal::next(*startNode); | |
146 ASSERT(startNode); | |
147 } else if (textOffset > 0) { | |
148 splitTextNode(textNode, textOffset); | |
149 } | |
150 } else if (pos.computeEditingOffset() > 0) { | |
151 Node* childAtOffset = NodeTraversal::childAt(*startNode, pos.computeEdit
ingOffset()); | |
152 startNode = childAtOffset ? childAtOffset : NodeTraversal::next(*startNo
de); | |
153 ASSERT(startNode); | |
154 } | |
155 | |
156 // If there's nothing inside topBlockquote to move, we're finished. | |
157 if (!startNode->isDescendantOf(topBlockquote)) { | |
158 setEndingSelection(VisibleSelection(VisiblePosition(firstPositionInOrBef
oreNode(startNode)), endingSelection().isDirectional())); | |
159 return; | |
160 } | |
161 | |
162 // Build up list of ancestors in between the start node and the top blockquo
te. | |
163 WillBeHeapVector<RefPtrWillBeMember<Element>> ancestors; | |
164 for (Element* node = startNode->parentElement(); node && node != topBlockquo
te; node = node->parentElement()) | |
165 ancestors.append(node); | |
166 | |
167 // Insert a clone of the top blockquote after the break. | |
168 RefPtrWillBeRawPtr<Element> clonedBlockquote = topBlockquote->cloneElementWi
thoutChildren(); | |
169 insertNodeAfter(clonedBlockquote.get(), breakElement.get()); | |
170 | |
171 // Clone startNode's ancestors into the cloned blockquote. | |
172 // On exiting this loop, clonedAncestor is the lowest ancestor | |
173 // that was cloned (i.e. the clone of either ancestors.last() | |
174 // or clonedBlockquote if ancestors is empty). | |
175 RefPtrWillBeRawPtr<Element> clonedAncestor = clonedBlockquote; | |
176 for (size_t i = ancestors.size(); i != 0; --i) { | |
177 RefPtrWillBeRawPtr<Element> clonedChild = ancestors[i - 1]->cloneElement
WithoutChildren(); | |
178 // Preserve list item numbering in cloned lists. | |
179 if (isHTMLOListElement(*clonedChild)) { | |
180 Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode; | |
181 // The first child of the cloned list might not be a list item eleme
nt, | |
182 // find the first one so that we know where to start numbering. | |
183 while (listChildNode && !isHTMLLIElement(*listChildNode)) | |
184 listChildNode = listChildNode->nextSibling(); | |
185 if (isListItem(listChildNode)) | |
186 setNodeAttribute(clonedChild, startAttr, AtomicString::number(to
LayoutListItem(listChildNode->layoutObject())->value())); | |
187 } | |
188 | |
189 appendNode(clonedChild.get(), clonedAncestor.get()); | |
190 clonedAncestor = clonedChild; | |
191 } | |
192 | |
193 moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor); | |
194 | |
195 if (!ancestors.isEmpty()) { | |
196 // Split the tree up the ancestor chain until the topBlockquote | |
197 // Throughout this loop, clonedParent is the clone of ancestor's parent. | |
198 // This is so we can clone ancestor's siblings and place the clones | |
199 // into the clone corresponding to the ancestor's parent. | |
200 RefPtrWillBeRawPtr<Element> ancestor = nullptr; | |
201 RefPtrWillBeRawPtr<Element> clonedParent = nullptr; | |
202 for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parent
Element(); | |
203 ancestor && ancestor != topBlockquote; | |
204 ancestor = ancestor->parentElement(), clonedParent = clonedParent->p
arentElement()) | |
205 moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedP
arent); | |
206 | |
207 // If the startNode's original parent is now empty, remove it | |
208 Element* originalParent = ancestors.first().get(); | |
209 if (!originalParent->hasChildren()) | |
210 removeNode(originalParent); | |
211 } | |
212 | |
213 // Make sure the cloned block quote renders. | |
214 addBlockPlaceholderIfNeeded(clonedBlockquote.get()); | |
215 | |
216 // Put the selection right before the break. | |
217 setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()),
DOWNSTREAM, endingSelection().isDirectional())); | |
218 rebalanceWhitespace(); | |
219 } | |
220 | |
221 } // namespace blink | |
OLD | NEW |