2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "BreakBlockquoteCommand.h"
30 #include "HTMLNames.h"
32 #include "VisiblePosition.h"
33 #include "htmlediting.h"
34 #include "RenderListItem.h"
38 using namespace HTMLNames
;
40 BreakBlockquoteCommand::BreakBlockquoteCommand(Document
*document
)
41 : CompositeEditCommand(document
)
45 void BreakBlockquoteCommand::doApply()
47 if (endingSelection().isNone())
50 // Delete the current selection.
51 if (endingSelection().isRange())
52 deleteSelection(false, false);
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();
59 // startNode is the first node that we need to move to the new blockquote.
60 Node
* startNode
= pos
.node();
61 // Find the top-most blockquote from the start.
62 Node
* topBlockquote
= 0;
63 for (Node
*node
= startNode
->parentNode(); node
; node
= node
->parentNode()) {
64 if (isMailBlockquote(node
))
67 if (!topBlockquote
|| !topBlockquote
->parentNode())
70 // Insert a break after the top blockquote.
71 RefPtr
<Element
> breakNode
= createBreakElement(document());
72 insertNodeAfter(breakNode
.get(), topBlockquote
);
74 if (isLastVisiblePositionInNode(visiblePos
, topBlockquote
)) {
75 setEndingSelection(Selection(Position(breakNode
.get(), 0), DOWNSTREAM
));
76 rebalanceWhitespace();
80 // Don't move a line break just after the caret. Doing so would create an extra, empty paragraph
81 // in the new blockquote.
82 if (lineBreakExistsAtPosition(visiblePos
))
85 // Split at pos if in the middle of a text node.
86 if (startNode
->isTextNode()) {
87 Text
* textNode
= static_cast<Text
*>(startNode
);
88 if ((unsigned)pos
.offset() >= textNode
->length()) {
89 startNode
= startNode
->traverseNextNode();
91 } else if (pos
.offset() > 0)
92 splitTextNode(textNode
, pos
.offset());
93 } else if (pos
.offset() > 0) {
94 startNode
= startNode
->traverseNextNode();
98 // If there's nothing inside topBlockquote to move, we're finished.
99 if (!startNode
->isDescendantOf(topBlockquote
)) {
100 setEndingSelection(Selection(VisiblePosition(Position(startNode
, 0))));
104 // Build up list of ancestors in between the start node and the top blockquote.
105 Vector
<Node
*> ancestors
;
106 for (Node
* node
= startNode
->parentNode(); node
!= topBlockquote
; node
= node
->parentNode())
107 ancestors
.append(node
);
109 // Insert a clone of the top blockquote after the break.
110 RefPtr
<Node
> clonedBlockquote
= topBlockquote
->cloneNode(false);
111 insertNodeAfter(clonedBlockquote
.get(), breakNode
.get());
113 // Clone startNode's ancestors into the cloned blockquote.
114 // On exiting this loop, clonedAncestor is the lowest ancestor
115 // that was cloned (i.e. the clone of either ancestors.last()
116 // or clonedBlockquote if ancestors is empty).
117 RefPtr
<Node
> clonedAncestor
= clonedBlockquote
;
118 for (size_t i
= ancestors
.size(); i
!= 0; --i
) {
119 RefPtr
<Node
> clonedChild
= ancestors
[i
- 1]->cloneNode(false); // shallow clone
120 // Preserve list item numbering in cloned lists.
121 if (clonedChild
->isElementNode() && clonedChild
->hasTagName(olTag
)) {
122 Node
* listChildNode
= i
> 1 ? ancestors
[i
- 2] : startNode
;
123 // The first child of the cloned list might not be a list item element,
124 // find the first one so that we know where to start numbering.
125 while (listChildNode
&& !listChildNode
->hasTagName(liTag
))
126 listChildNode
= listChildNode
->nextSibling();
127 if (listChildNode
&& listChildNode
->renderer())
128 setNodeAttribute(static_cast<Element
*>(clonedChild
.get()), startAttr
, String::number(static_cast<RenderListItem
*>(listChildNode
->renderer())->value()));
131 appendNode(clonedChild
.get(), clonedAncestor
.get());
132 clonedAncestor
= clonedChild
;
135 // Move the startNode and its siblings.
136 Node
*moveNode
= startNode
;
138 Node
*next
= moveNode
->nextSibling();
139 removeNode(moveNode
);
140 appendNode(moveNode
, clonedAncestor
.get());
144 // Hold open startNode's original parent if we emptied it
145 if (!ancestors
.isEmpty()) {
146 addBlockPlaceholderIfNeeded(ancestors
.first());
148 // Split the tree up the ancestor chain until the topBlockquote
149 // Throughout this loop, clonedParent is the clone of ancestor's parent.
150 // This is so we can clone ancestor's siblings and place the clones
151 // into the clone corresponding to the ancestor's parent.
154 for (ancestor
= ancestors
.first(), clonedParent
= clonedAncestor
->parentNode();
155 ancestor
&& ancestor
!= topBlockquote
;
156 ancestor
= ancestor
->parentNode(), clonedParent
= clonedParent
->parentNode()) {
157 moveNode
= ancestor
->nextSibling();
159 Node
*next
= moveNode
->nextSibling();
160 removeNode(moveNode
);
161 appendNode(moveNode
, clonedParent
);
167 // Make sure the cloned block quote renders.
168 addBlockPlaceholderIfNeeded(clonedBlockquote
.get());
170 // Put the selection right before the break.
171 setEndingSelection(Selection(Position(breakNode
.get(), 0), DOWNSTREAM
));
172 rebalanceWhitespace();
175 } // namespace WebCore