[Cherry-pick] Add a new set of WebKit2 APIs for text search and search results manage...
[framework/web/webkit-efl.git] / Source / WebCore / editing / InsertTextCommand.cpp
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 "InsertTextCommand.h"
28
29 #include "Document.h"
30 #include "Element.h"
31 #include "EditingText.h"
32 #include "Editor.h"
33 #include "Frame.h"
34 #include "HTMLInterchange.h"
35 #include "htmlediting.h"
36 #include "visible_units.h"
37 #include <wtf/unicode/CharacterNames.h>
38
39 namespace WebCore {
40
41 InsertTextCommand::InsertTextCommand(Document* document, const String& text, bool selectInsertedText, RebalanceType rebalanceType) 
42     : CompositeEditCommand(document)
43     , m_text(text)
44     , m_selectInsertedText(selectInsertedText)
45     , m_rebalanceType(rebalanceType)
46 {
47 }
48
49 InsertTextCommand::InsertTextCommand(Document* document, const String& text, PassRefPtr<TextInsertionMarkerSupplier> markerSupplier)
50     : CompositeEditCommand(document)
51     , m_text(text)
52     , m_selectInsertedText(false)
53     , m_rebalanceType(RebalanceLeadingAndTrailingWhitespaces)
54     , m_markerSupplier(markerSupplier)
55 {
56 }
57
58 Position InsertTextCommand::positionInsideTextNode(const Position& p)
59 {
60     Position pos = p;
61     if (isTabSpanTextNode(pos.anchorNode())) {
62         RefPtr<Node> textNode = document()->createEditingTextNode("");
63         insertNodeAtTabSpanPosition(textNode.get(), pos);
64         return firstPositionInNode(textNode.get());
65     }
66
67     // Prepare for text input by looking at the specified position.
68     // It may be necessary to insert a text node to receive characters.
69     if (!pos.containerNode()->isTextNode()) {
70         RefPtr<Node> textNode = document()->createEditingTextNode("");
71         insertNodeAt(textNode.get(), pos);
72         return firstPositionInNode(textNode.get());
73     }
74
75     return pos;
76 }
77
78 // This avoids the expense of a full fledged delete operation, and avoids a layout that typically results
79 // from text removal.
80 bool InsertTextCommand::performTrivialReplace(const String& text, bool selectInsertedText)
81 {
82     if (!endingSelection().isRange())
83         return false;
84     
85     if (text.contains('\t') || text.contains(' ') || text.contains('\n'))
86         return false;
87
88     Position start = endingSelection().start();
89     Position endPosition = replaceSelectedTextInNode(text);
90     if (endPosition.isNull())
91         return false;
92
93     // We could have inserted a part of composed character sequence,
94     // so we are basically treating ending selection as a range to avoid validation.
95     // <http://bugs.webkit.org/show_bug.cgi?id=15781>
96     VisibleSelection forcedEndingSelection;
97     forcedEndingSelection.setWithoutValidation(start, endPosition);
98     forcedEndingSelection.setIsDirectional(endingSelection().isDirectional());
99     setEndingSelection(forcedEndingSelection);
100
101     if (!selectInsertedText)
102         setEndingSelection(VisibleSelection(endingSelection().visibleEnd(), endingSelection().isDirectional()));
103     
104     return true;
105 }
106
107 void InsertTextCommand::doApply()
108 {
109     ASSERT(m_text.find('\n') == notFound);
110
111     if (!endingSelection().isNonOrphanedCaretOrRange())
112         return;
113
114     // Delete the current selection.
115     // FIXME: This delete operation blows away the typing style.
116     if (endingSelection().isRange()) {
117         if (performTrivialReplace(m_text, m_selectInsertedText))
118             return;
119         deleteSelection(false, true, true, false, false);
120         // deleteSelection eventually makes a new endingSelection out of a Position. If that Position doesn't have
121         // a renderer (e.g. it is on a <frameset> in the DOM), the VisibleSelection cannot be canonicalized to 
122         // anything other than NoSelection. The rest of this function requires a real endingSelection, so bail out.
123         if (endingSelection().isNone())
124             return;
125     }
126
127     Position startPosition(endingSelection().start());
128     
129     Position placeholder;
130     // We want to remove preserved newlines and brs that will collapse (and thus become unnecessary) when content 
131     // is inserted just before them.
132     // FIXME: We shouldn't really have to do this, but removing placeholders is a workaround for 9661.
133     // If the caret is just before a placeholder, downstream will normalize the caret to it.
134     Position downstream(startPosition.downstream());
135     if (lineBreakExistsAtPosition(downstream)) {
136         // FIXME: This doesn't handle placeholders at the end of anonymous blocks.
137         VisiblePosition caret(startPosition);
138         if (isEndOfBlock(caret) && isStartOfParagraph(caret))
139             placeholder = downstream;
140         // Don't remove the placeholder yet, otherwise the block we're inserting into would collapse before
141         // we get a chance to insert into it.  We check for a placeholder now, though, because doing so requires
142         // the creation of a VisiblePosition, and if we did that post-insertion it would force a layout.
143     }
144     
145     // Insert the character at the leftmost candidate.
146     startPosition = startPosition.upstream();
147     
148     // It is possible for the node that contains startPosition to contain only unrendered whitespace,
149     // and so deleteInsignificantText could remove it.  Save the position before the node in case that happens.
150     Position positionBeforeStartNode(positionInParentBeforeNode(startPosition.containerNode()));
151     deleteInsignificantText(startPosition.upstream(), startPosition.downstream());
152     if (!startPosition.anchorNode()->inDocument())
153         startPosition = positionBeforeStartNode;
154     if (!startPosition.isCandidate())
155         startPosition = startPosition.downstream();
156     
157     startPosition = positionAvoidingSpecialElementBoundary(startPosition);
158     
159     Position endPosition;
160     
161     if (m_text == "\t") {
162         endPosition = insertTab(startPosition);
163         startPosition = endPosition.previous();
164         if (placeholder.isNotNull())
165             removePlaceholderAt(placeholder);
166     } else {
167         // Make sure the document is set up to receive m_text
168         startPosition = positionInsideTextNode(startPosition);
169         ASSERT(startPosition.anchorType() == Position::PositionIsOffsetInAnchor);
170         ASSERT(startPosition.containerNode());
171         ASSERT(startPosition.containerNode()->isTextNode());
172         if (placeholder.isNotNull())
173             removePlaceholderAt(placeholder);
174         RefPtr<Text> textNode = startPosition.containerText();
175         const unsigned offset = startPosition.offsetInContainerNode();
176
177         insertTextIntoNode(textNode, offset, m_text);
178         endPosition = Position(textNode, offset + m_text.length());
179         if (m_markerSupplier)
180             m_markerSupplier->addMarkersToTextNode(textNode.get(), offset, m_text);
181
182         if (m_rebalanceType == RebalanceLeadingAndTrailingWhitespaces) {
183             // The insertion may require adjusting adjacent whitespace, if it is present.
184             rebalanceWhitespaceAt(endPosition);
185             // Rebalancing on both sides isn't necessary if we've inserted only spaces.
186             if (!shouldRebalanceLeadingWhitespaceFor(m_text))
187                 rebalanceWhitespaceAt(startPosition);
188         } else {
189             ASSERT(m_rebalanceType == RebalanceAllWhitespaces);
190             if (canRebalance(startPosition) && canRebalance(endPosition))
191                 rebalanceWhitespaceOnTextSubstring(textNode, startPosition.offsetInContainerNode(), endPosition.offsetInContainerNode());
192         }
193     }
194
195     // We could have inserted a part of composed character sequence,
196     // so we are basically treating ending selection as a range to avoid validation.
197     // <http://bugs.webkit.org/show_bug.cgi?id=15781>
198     VisibleSelection forcedEndingSelection;
199     forcedEndingSelection.setWithoutValidation(startPosition, endPosition);
200     forcedEndingSelection.setIsDirectional(endingSelection().isDirectional());
201     setEndingSelection(forcedEndingSelection);
202
203     // Handle the case where there is a typing style.
204     if (RefPtr<EditingStyle> typingStyle = document()->frame()->selection()->typingStyle()) {
205         typingStyle->prepareToApplyAt(endPosition, EditingStyle::PreserveWritingDirection);
206         if (!typingStyle->isEmpty())
207             applyStyle(typingStyle.get());
208     }
209
210     if (!m_selectInsertedText)
211         setEndingSelection(VisibleSelection(endingSelection().end(), endingSelection().affinity(), endingSelection().isDirectional()));
212 }
213
214 Position InsertTextCommand::insertTab(const Position& pos)
215 {
216     Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent();
217
218     Node* node = insertPos.containerNode();
219     unsigned int offset = node->isTextNode() ? insertPos.offsetInContainerNode() : 0;
220
221     // keep tabs coalesced in tab span
222     if (isTabSpanTextNode(node)) {
223         RefPtr<Text> textNode = toText(node);
224         insertTextIntoNode(textNode, offset, "\t");
225         return Position(textNode.release(), offset + 1);
226     }
227     
228     // create new tab span
229     RefPtr<Element> spanNode = createTabSpanElement(document());
230     
231     // place it
232     if (!node->isTextNode()) {
233         insertNodeAt(spanNode.get(), insertPos);
234     } else {
235         RefPtr<Text> textNode = toText(node);
236         if (offset >= textNode->length())
237             insertNodeAfter(spanNode, textNode.release());
238         else {
239             // split node to make room for the span
240             // NOTE: splitTextNode uses textNode for the
241             // second node in the split, so we need to
242             // insert the span before it.
243             if (offset > 0)
244                 splitTextNode(textNode, offset);
245             insertNodeBefore(spanNode, textNode.release());
246         }
247     }
248
249     // return the position following the new tab
250     return lastPositionInNode(spanNode.get());
251 }
252
253 }