WebCore:
[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         Node *refNode;
172         if (isFirstInBlock && !nestNewBlock)
173             refNode = startBlock;
174         else if (pos.node() == startBlock && nestNewBlock) {
175             refNode = startBlock->childNode(pos.offset());
176             ASSERT(refNode); // must be true or we'd be in the end of block case
177         } else
178             refNode = pos.node();
179
180         // find ending selection position easily before inserting the paragraph
181         pos = pos.downstream();
182         
183         insertNodeBefore(blockToInsert.get(), refNode);
184         appendBlockPlaceholder(blockToInsert.get());
185         setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM));
186         applyStyleAfterInsertion();
187         setEndingSelection(Selection(pos, DOWNSTREAM));
188         return;
189     }
190
191     //---------------------------------------------------------------------
192     // Handle the (more complicated) general case,
193
194     // All of the content in the current block after visiblePos is
195     // about to be wrapped in a new paragraph element.  Add a br before 
196     // it if visiblePos is at the start of a paragraph so that the 
197     // content will move down a line.
198     if (isStartOfParagraph(visiblePos)) {
199         RefPtr<Element> br = createBreakElement(document());
200         insertNodeAt(br.get(), pos);
201         pos = positionAfterNode(br.get());
202     }
203     
204     // Move downstream. Typing style code will take care of carrying along the 
205     // style of the upstream position.
206     pos = pos.downstream();
207     startNode = pos.node();
208
209     // Build up list of ancestors in between the start node and the start block.
210     Vector<Node*> ancestors;
211     if (startNode != startBlock)
212         for (Node* n = startNode->parentNode(); n && n != startBlock; n = n->parentNode())
213             ancestors.append(n);
214
215     // Make sure we do not cause a rendered space to become unrendered.
216     // FIXME: We need the affinity for pos, but pos.downstream() does not give it
217     Position leadingWhitespace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
218     // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions
219     // after the preserved newline, causing the newline to be turned into a nbsp.
220     if (leadingWhitespace.isNotNull()) {
221         Text* textNode = static_cast<Text*>(leadingWhitespace.node());
222         ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
223         replaceTextInNode(textNode, leadingWhitespace.offset(), 1, nonBreakingSpaceString());
224     }
225     
226     // Split at pos if in the middle of a text node.
227     if (startNode->isTextNode()) {
228         Text *textNode = static_cast<Text *>(startNode);
229         bool atEnd = (unsigned)pos.offset() >= textNode->length();
230         if (pos.offset() > 0 && !atEnd) {
231             splitTextNode(textNode, pos.offset());
232             pos = Position(startNode, 0);
233             visiblePos = VisiblePosition(pos);
234             splitText = true;
235         }
236     }
237
238     // Put the added block in the tree.
239     if (nestNewBlock)
240         appendNode(blockToInsert.get(), startBlock);
241     else
242         insertNodeAfter(blockToInsert.get(), startBlock);
243
244     updateLayout();
245     
246     // Make clones of ancestors in between the start node and the start block.
247     RefPtr<Node> parent = blockToInsert;
248     for (size_t i = ancestors.size(); i != 0; --i) {
249         RefPtr<Node> child = ancestors[i - 1]->cloneNode(false); // shallow clone
250         appendNode(child.get(), parent.get());
251         parent = child.release();
252     }
253
254     // If the paragraph separator was inserted at the end of a paragraph, an empty line must be
255     // created.  All of the nodes, starting at visiblePos, are about to be added to the new paragraph 
256     // element.  If the first node to be inserted won't be one that will hold an empty line open, add a br.
257     if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtPosition(visiblePos))
258         appendNode(createBreakElement(document()).get(), blockToInsert.get());
259         
260     // Move the start node and the siblings of the start node.
261     if (startNode != startBlock) {
262         Node *n = startNode;
263         if (pos.offset() >= startNode->caretMaxOffset())
264             n = startNode->nextSibling();
265
266         while (n && n != blockToInsert) {
267             Node *next = n->nextSibling();
268             removeNode(n);
269             appendNode(n, parent.get());
270             n = next;
271         }
272     }            
273
274     // Move everything after the start node.
275     if (!ancestors.isEmpty()) {
276         Node* leftParent = ancestors.first();
277         while (leftParent && leftParent != startBlock) {
278             parent = parent->parentNode();
279             Node* n = leftParent->nextSibling();
280             while (n && n != blockToInsert) {
281                 Node* next = n->nextSibling();
282                 removeNode(n);
283                 appendNode(n, parent.get());
284                 n = next;
285             }
286             leftParent = leftParent->parentNode();
287         }
288     }
289
290     // Handle whitespace that occurs after the split
291     if (splitText) {
292         updateLayout();
293         pos = Position(startNode, 0);
294         if (!pos.isRenderedCharacter()) {
295             // Clear out all whitespace and insert one non-breaking space
296             ASSERT(startNode);
297             ASSERT(startNode->isTextNode());
298             ASSERT(!startNode->renderer() || startNode->renderer()->style()->collapseWhiteSpace());
299             deleteInsignificantTextDownstream(pos);
300             insertTextIntoNode(static_cast<Text*>(startNode), 0, nonBreakingSpaceString());
301         }
302     }
303
304     setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM));
305     applyStyleAfterInsertion();
306 }
307
308 } // namespace WebCore