d2e1643171f85f44833eae69fea2c8634caa82d5
[WebKit-https.git] / WebCore / editing / InsertParagraphSeparatorCommand.cpp
1 /*
2  * Copyright (C) 2005, 2006 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 "InsertParagraphSeparatorCommand.h"
28
29 #include "Document.h"
30 #include "Logging.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSPropertyNames.h"
33 #include "Text.h"
34 #include "htmlediting.h"
35 #include "HTMLElement.h"
36 #include "HTMLNames.h"
37 #include "InsertLineBreakCommand.h"
38 #include "RenderObject.h"
39 #include "visible_units.h"
40
41 namespace WebCore {
42
43 using namespace HTMLNames;
44
45 InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(Document *document, bool useDefaultParagraphElement) 
46     : CompositeEditCommand(document)
47     , m_useDefaultParagraphElement(useDefaultParagraphElement)
48 {
49 }
50
51 bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
52 {
53     return true;
54 }
55
56 void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
57 {
58     // It is only important to set a style to apply later if we're at the boundaries of
59     // a paragraph. Otherwise, content that is moved as part of the work of the command
60     // will lend their styles to the new paragraph without any extra work needed.
61     VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
62     if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos))
63         return;
64     
65     m_style = styleAtPosition(pos);
66 }
67
68 void InsertParagraphSeparatorCommand::applyStyleAfterInsertion()
69 {
70     // FIXME: Improve typing style.
71     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
72     if (!m_style)
73         return;
74
75     CSSComputedStyleDeclaration endingStyle(endingSelection().start().node());
76     endingStyle.diff(m_style.get());
77     if (m_style->length() > 0)
78         applyStyle(m_style.get());
79 }
80
81 void InsertParagraphSeparatorCommand::doApply()
82 {
83     bool splitText = false;
84     if (endingSelection().isNone())
85         return;
86     
87     Position pos = endingSelection().start();
88         
89     EAffinity affinity = endingSelection().affinity();
90         
91     // Delete the current selection.
92     if (endingSelection().isRange()) {
93         calculateStyleBeforeInsertion(pos);
94         deleteSelection(false, true);
95         pos = endingSelection().start();
96         affinity = endingSelection().affinity();
97     }
98     
99     // FIXME: Turn into an InsertLineBreak in other cases where we don't want to do the splitting/cloning that
100     // InsertParagraphSeparator does.
101     Node* block = enclosingBlock(pos.node());
102     Position canonicalPos = VisiblePosition(pos).deepEquivalent();
103     if (!block || !block->parentNode() || 
104         block->renderer() && block->renderer()->isTableCell() ||
105         canonicalPos.node()->renderer() && canonicalPos.node()->renderer()->isTable() ||
106         canonicalPos.node()->hasTagName(hrTag)) {
107         applyCommandToComposite(new InsertLineBreakCommand(document()));
108         return;
109     }
110     
111     // Use the leftmost candidate.
112     pos = pos.upstream();
113     if (!pos.isCandidate())
114         pos = pos.downstream();
115
116     // Adjust the insertion position after the delete
117     pos = positionAvoidingSpecialElementBoundary(pos);
118     VisiblePosition visiblePos(pos, affinity);
119     calculateStyleBeforeInsertion(pos);
120
121     //---------------------------------------------------------------------
122     // Handle special case of typing return on an empty list item
123     if (breakOutOfEmptyListItem())
124         return;
125
126     //---------------------------------------------------------------------
127     // Prepare for more general cases.
128     Node *startNode = pos.node();
129     Node *startBlock = startNode->enclosingBlockFlowElement();
130
131     bool isFirstInBlock = isStartOfBlock(visiblePos);
132     bool isLastInBlock = isEndOfBlock(visiblePos);
133     bool nestNewBlock = false;
134
135     // Create block to be inserted.
136     RefPtr<Node> blockToInsert;
137     if (startBlock == startBlock->rootEditableElement()) {
138         blockToInsert = static_pointer_cast<Node>(createDefaultParagraphElement(document()));
139         nestNewBlock = true;
140     } else if (m_useDefaultParagraphElement)
141         blockToInsert = static_pointer_cast<Node>(createDefaultParagraphElement(document()));
142     else
143         blockToInsert = startBlock->cloneNode(false);
144     
145     //---------------------------------------------------------------------
146     // Handle case when position is in the last visible position in its block,
147     // including when the block is empty. 
148     if (isLastInBlock) {
149         if (nestNewBlock) {
150             if (isFirstInBlock) {
151                 // block is empty: create an empty paragraph to
152                 // represent the content before the new one.
153                 RefPtr<Node> extraBlock = createDefaultParagraphElement(document());
154                 appendNode(extraBlock.get(), startBlock);
155                 appendBlockPlaceholder(extraBlock.get());
156             }
157             appendNode(blockToInsert.get(), startBlock);
158         } else
159             insertNodeAfter(blockToInsert.get(), startBlock);
160
161         appendBlockPlaceholder(blockToInsert.get());
162         setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM));
163         applyStyleAfterInsertion();
164         return;
165     }
166
167     //---------------------------------------------------------------------
168     // Handle case when position is in the first visible position in its block, and
169     // similar case where previous position is in another, presumeably nested, block.
170     if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) {
171         pos = pos.downstream();
172         Node *refNode;
173         if (isFirstInBlock && !nestNewBlock)
174             refNode = startBlock;
175         else if (pos.node() == startBlock && nestNewBlock) {
176             refNode = startBlock->childNode(pos.offset());
177             ASSERT(refNode); // must be true or we'd be in the end of block case
178         } else
179             refNode = pos.node();
180
181         insertNodeBefore(blockToInsert.get(), refNode);
182         appendBlockPlaceholder(blockToInsert.get());
183         setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM));
184         applyStyleAfterInsertion();
185         setEndingSelection(Selection(pos, DOWNSTREAM));
186         return;
187     }
188
189     //---------------------------------------------------------------------
190     // Handle the (more complicated) general case,
191
192     // All of the content in the current block after visiblePos is
193     // about to be wrapped in a new paragraph element.  Add a br before 
194     // it if visiblePos is at the start of a paragraph so that the 
195     // content will move down a line.
196     if (isStartOfParagraph(visiblePos)) {
197         RefPtr<Element> br = createBreakElement(document());
198         insertNodeAt(br.get(), pos);
199         pos = positionAfterNode(br.get());
200     }
201     
202     // Move downstream. Typing style code will take care of carrying along the 
203     // style of the upstream position.
204     pos = pos.downstream();
205     startNode = pos.node();
206
207     // Build up list of ancestors in between the start node and the start block.
208     Vector<Node*> ancestors;
209     if (startNode != startBlock)
210         for (Node* n = startNode->parentNode(); n && n != startBlock; n = n->parentNode())
211             ancestors.append(n);
212
213     // Make sure we do not cause a rendered space to become unrendered.
214     // FIXME: We need the affinity for pos, but pos.downstream() does not give it
215     Position leadingWhitespace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
216     // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions
217     // after the preserved newline, causing the newline to be turned into a nbsp.
218     if (leadingWhitespace.isNotNull()) {
219         Text* textNode = static_cast<Text*>(leadingWhitespace.node());
220         ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
221         replaceTextInNode(textNode, leadingWhitespace.offset(), 1, nonBreakingSpaceString());
222     }
223     
224     // Split at pos if in the middle of a text node.
225     if (startNode->isTextNode()) {
226         Text *textNode = static_cast<Text *>(startNode);
227         bool atEnd = (unsigned)pos.offset() >= textNode->length();
228         if (pos.offset() > 0 && !atEnd) {
229             splitTextNode(textNode, pos.offset());
230             pos = Position(startNode, 0);
231             visiblePos = VisiblePosition(pos);
232             splitText = true;
233         }
234     }
235
236     // Put the added block in the tree.
237     if (nestNewBlock)
238         appendNode(blockToInsert.get(), startBlock);
239     else
240         insertNodeAfter(blockToInsert.get(), startBlock);
241
242     updateLayout();
243     
244     // Make clones of ancestors in between the start node and the start block.
245     RefPtr<Node> parent = blockToInsert;
246     for (size_t i = ancestors.size(); i != 0; --i) {
247         RefPtr<Node> child = ancestors[i - 1]->cloneNode(false); // shallow clone
248         appendNode(child.get(), parent.get());
249         parent = child.release();
250     }
251
252     // If the paragraph separator was inserted at the end of a paragraph, an empty line must be
253     // created.  All of the nodes, starting at visiblePos, are about to be added to the new paragraph 
254     // element.  If the first node to be inserted won't be one that will hold an empty line open, add a br.
255     if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtPosition(visiblePos))
256         appendNode(createBreakElement(document()).get(), blockToInsert.get());
257         
258     // Move the start node and the siblings of the start node.
259     if (startNode != startBlock) {
260         Node *n = startNode;
261         if (pos.offset() >= startNode->caretMaxOffset())
262             n = startNode->nextSibling();
263
264         while (n && n != blockToInsert) {
265             Node *next = n->nextSibling();
266             removeNode(n);
267             appendNode(n, parent.get());
268             n = next;
269         }
270     }            
271
272     // Move everything after the start node.
273     if (!ancestors.isEmpty()) {
274         Node* leftParent = ancestors.first();
275         while (leftParent && leftParent != startBlock) {
276             parent = parent->parentNode();
277             Node* n = leftParent->nextSibling();
278             while (n && n != blockToInsert) {
279                 Node* next = n->nextSibling();
280                 removeNode(n);
281                 appendNode(n, parent.get());
282                 n = next;
283             }
284             leftParent = leftParent->parentNode();
285         }
286     }
287
288     // Handle whitespace that occurs after the split
289     if (splitText) {
290         updateLayout();
291         pos = Position(startNode, 0);
292         if (!pos.isRenderedCharacter()) {
293             // Clear out all whitespace and insert one non-breaking space
294             ASSERT(startNode);
295             ASSERT(startNode->isTextNode());
296             ASSERT(!startNode->renderer() || startNode->renderer()->style()->collapseWhiteSpace());
297             deleteInsignificantTextDownstream(pos);
298             insertTextIntoNode(static_cast<Text*>(startNode), 0, nonBreakingSpaceString());
299         }
300     }
301
302     setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM));
303     applyStyleAfterInsertion();
304 }
305
306 } // namespace WebCore