13eb3b5c42118a95b1ed28877b425ef3f545fdf3
[WebKit-https.git] / WebCore / khtml / editing / insert_text_command.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 "insert_text_command.h"
27
28 #include "khtml_part.h"
29 #include "htmlediting.h"
30 #include "html_interchange.h"
31 #include "visible_position.h"
32 #include "visible_text.h"
33 #include "visible_units.h"
34 #include "xml/dom_docimpl.h"
35 #include "xml/dom_position.h"
36 #include "xml/dom_textimpl.h"
37
38 #if APPLE_CHANGES
39 #include <kxmlcore/Assertions.h>
40 #include "KWQLogging.h"
41 #else
42 #define ASSERT(assertion) assert(assertion)
43 #define LOG(channel, formatAndArgs...) ((void)0)
44 #endif
45
46 using DOM::DocumentImpl;
47 using DOM::NodeImpl;
48 using DOM::Position;
49 using DOM::TextImpl;
50 using DOM::DOMString;
51 using DOM::CSSMutableStyleDeclarationImpl;
52
53 namespace khtml {
54
55 InsertTextCommand::InsertTextCommand(DocumentImpl *document) 
56     : CompositeEditCommand(document), m_charactersAdded(0)
57 {
58 }
59
60 void InsertTextCommand::doApply()
61 {
62 }
63
64 Position InsertTextCommand::prepareForTextInsertion(const Position& pos)
65 {
66     // Prepare for text input by looking at the specified position.
67     // It may be necessary to insert a text node to receive characters.
68     if (!pos.node()->isTextNode()) {
69         NodeImpl *textNode = document()->createEditingTextNode("");
70         NodeImpl *nodeToInsert = textNode;
71
72         // Now insert the node in the right place
73         if (pos.node()->rootEditableElement() != NULL) {
74             LOG(Editing, "prepareForTextInsertion case 1");
75             insertNodeAt(nodeToInsert, pos.node(), pos.offset());
76         }
77         else if (pos.node()->caretMinOffset() == pos.offset()) {
78             LOG(Editing, "prepareForTextInsertion case 2");
79             insertNodeBefore(nodeToInsert, pos.node());
80         }
81         else if (pos.node()->caretMaxOffset() == pos.offset()) {
82             LOG(Editing, "prepareForTextInsertion case 3");
83             insertNodeAfter(nodeToInsert, pos.node());
84         }
85         else
86             ASSERT_NOT_REACHED();
87         
88         return Position(textNode, 0);
89     }
90
91     if (isTabSpanTextNode(pos.node())) {
92         Position tempPos = pos;
93 //#ifndef COALESCE_TAB_SPANS
94 #if 0
95         NodeImpl *node = pos.node()->parentNode();
96         if (pos.offset() > pos.node()->caretMinOffset())
97             tempPos = Position(node->parentNode(), node->nodeIndex() + 1);
98         else
99             tempPos = Position(node->parentNode(), node->nodeIndex());
100 #endif        
101         NodeImpl *textNode = document()->createEditingTextNode("");
102         NodeImpl *originalTabSpan = tempPos.node()->parent();
103         if (tempPos.offset() <= tempPos.node()->caretMinOffset()) {
104             insertNodeBefore(textNode, originalTabSpan);
105         } else if (tempPos.offset() >= tempPos.node()->caretMaxOffset()) {
106             insertNodeAfter(textNode, originalTabSpan);
107         } else {
108             splitTextNodeContainingElement(static_cast<TextImpl *>(tempPos.node()), tempPos.offset());
109             insertNodeBefore(textNode, originalTabSpan);
110         }
111         return Position(textNode, 0);
112     }
113
114     return pos;
115 }
116
117 static inline bool isNBSP(const QChar &c)
118 {
119     return c.unicode() == 0xa0;
120 }
121
122 void InsertTextCommand::input(const DOMString &text, bool selectInsertedText)
123 {
124     assert(text.find('\n') == -1);
125
126     SelectionController selection = endingSelection();
127     bool adjustDownstream = isStartOfLine(VisiblePosition(selection.start().downstream(), DOWNSTREAM));
128
129     // Delete the current selection, or collapse whitespace, as needed
130     if (selection.isRange())
131         deleteSelection();
132     
133     // Delete any insignificant text that could get in the way of whitespace turning
134     // out correctly after the insertion.
135     selection = endingSelection();
136     deleteInsignificantTextDownstream(selection.end().trailingWhitespacePosition(selection.endAffinity()));
137
138     // Figure out the startPosition
139     Position startPosition = selection.start();
140     Position endPosition;
141     if (adjustDownstream)
142         startPosition = startPosition.downstream();
143     else
144         startPosition = startPosition.upstream();
145     startPosition = positionOutsideContainingSpecialElement(startPosition);
146     
147     if (text == "\t") {
148         endPosition = insertTab(startPosition);
149         startPosition = endPosition.previous();
150         removeBlockPlaceholder(startPosition.node()->enclosingBlockFlowElement());
151         m_charactersAdded += 1;
152     } else {
153         // Make sure the document is set up to receive text
154         startPosition = prepareForTextInsertion(startPosition);
155         removeBlockPlaceholder(startPosition.node()->enclosingBlockFlowElement());
156         TextImpl *textNode = static_cast<TextImpl *>(startPosition.node());
157         int offset = startPosition.offset();
158
159         if (text == " ") {
160             insertSpace(textNode, offset);
161             endPosition = Position(textNode, offset + 1);
162
163             m_charactersAdded++;
164             rebalanceWhitespace();
165         }
166         else {
167             const DOMString &existingText = textNode->data();
168             if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isCollapsibleWhitespace(existingText[offset - 2])) {
169                 // DOM looks like this:
170                 // character nbsp caret
171                 // As we are about to insert a non-whitespace character at the caret
172                 // convert the nbsp to a regular space.
173                 // EDIT FIXME: This needs to be improved some day to convert back only
174                 // those nbsp's added by the editor to make rendering come out right.
175                 replaceTextInNode(textNode, offset - 1, 1, " ");
176             }
177             unsigned int len = text.length();
178             
179 #if APPLE_CHANGES
180             // When the user hits space to finish marked sequence, the string that
181             // we receive ends with a normal space, not a non breaking space.  This code
182             // ensures that the right kind of space is produced.
183             if (KWQ(document()->part())->markedTextRange() && text[len-1] == ' ') {
184                 DOMString textWithoutTrailingSpace(text.unicode(), len-1);
185                 insertTextIntoNode(textNode, offset, textWithoutTrailingSpace);
186                 insertSpace(textNode, offset + len-1);
187             } else
188                 insertTextIntoNode(textNode, offset, text);
189 #else
190             insertTextIntoNode(textNode, offset, text);
191 #endif
192             m_charactersAdded += len;
193             endPosition = Position(textNode, offset + len);
194         }
195     }
196
197     setEndingSelection(SelectionController(startPosition, DOWNSTREAM, endPosition, SEL_DEFAULT_AFFINITY));
198
199     // Handle the case where there is a typing style.
200     // FIXME: Improve typing style.
201     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
202     CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
203     if (typingStyle && typingStyle->length() > 0)
204         applyStyle(typingStyle);
205
206     if (!selectInsertedText)
207         setEndingSelection(endingSelection().end(), endingSelection().endAffinity());
208 }
209
210 DOM::Position InsertTextCommand::insertTab(Position pos)
211 {
212     Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent();
213     NodeImpl *node = insertPos.node();
214     unsigned int offset = insertPos.offset();
215
216 //#ifdef COALESCE_TAB_SPANS
217 #if 1
218     // keep tabs coalesced in tab span
219     if (isTabSpanTextNode(node)) {
220         insertTextIntoNode(static_cast<TextImpl *>(node), offset, "\t");
221         return Position(node, offset + 1);
222     }
223 #else
224     if (isTabSpanTextNode(node)) {
225         node = node->parentNode();
226         if (offset > (unsigned int) node->caretMinOffset())
227             insertPos = Position(node->parentNode(), node->nodeIndex() + 1);
228         else
229             insertPos = Position(node->parentNode(), node->nodeIndex());
230         node = insertPos.node();
231         offset = insertPos.offset();
232     }
233 #endif
234     
235     // create new tab span
236     DOM::ElementImpl * spanNode = createTabSpanElement(document());
237     
238     // place it
239     if (!node->isTextNode()) {
240         insertNodeAt(spanNode, node, offset);
241     } else {
242         TextImpl *textNode = static_cast<TextImpl *>(node);
243         if (offset >= textNode->length()) {
244             insertNodeAfter(spanNode, textNode);
245         } else {
246             // split node to make room for the span
247             // NOTE: splitTextNode uses textNode for the
248             // second node in the split, so we need to
249             // insert the span before it.
250             if (offset > 0)
251                 splitTextNode(textNode, offset);
252             insertNodeBefore(spanNode, textNode);
253         }
254     }
255     
256     // return the position following the new tab
257     return Position(spanNode->lastChild(), spanNode->lastChild()->caretMaxOffset());
258 }
259
260 void InsertTextCommand::insertSpace(TextImpl *textNode, unsigned offset)
261 {
262     ASSERT(textNode);
263
264     DOMString text(textNode->data());
265
266     // count up all spaces and newlines in front of the caret
267     // delete all collapsed ones
268     // this will work out OK since the offset we have been passed has been upstream-ized 
269     int count = 0;
270     for (unsigned int i = offset; i < text.length(); i++) {
271         if (isCollapsibleWhitespace(text[i]))
272             count++;
273         else 
274             break;
275     }
276     if (count > 0) {
277         // By checking the character at the downstream position, we can
278         // check if there is a rendered WS at the caret
279         Position pos(textNode, offset);
280         Position downstream = pos.downstream();
281         if (downstream.offset() < (int)text.length() && isCollapsibleWhitespace(text[downstream.offset()]))
282             count--; // leave this WS in
283         if (count > 0)
284             deleteTextFromNode(textNode, offset, count);
285     }
286
287     if (offset > 0 && offset <= text.length() - 1 && !isCollapsibleWhitespace(text[offset]) && !isCollapsibleWhitespace(text[offset - 1])) {
288         // insert a "regular" space
289         insertTextIntoNode(textNode, offset, " ");
290         return;
291     }
292
293     if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
294         // DOM looks like this:
295         // nbsp nbsp caret
296         // insert a space between the two nbsps
297         insertTextIntoNode(textNode, offset - 1, " ");
298         return;
299     }
300
301     // insert an nbsp
302     insertTextIntoNode(textNode, offset, nonBreakingSpaceString());
303 }
304
305 bool InsertTextCommand::isInsertTextCommand() const
306 {
307     return true;
308 }
309
310 } // namespace khtml