Reviewed by harrison
[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         insertTextIntoNode(textNode, offset, text);
160         endPosition = Position(textNode, offset + text.length());
161
162         // The insertion may require adjusting adjacent whitespace, if it is present.
163         rebalanceWhitespaceAt(endPosition);
164         // Rebalancing on both sides isn't necessary if we've inserted a space.
165         if (text != " ") 
166             rebalanceWhitespaceAt(startPosition);
167             
168         m_charactersAdded += text.length();
169     }
170
171     setEndingSelection(SelectionController(startPosition, DOWNSTREAM, endPosition, SEL_DEFAULT_AFFINITY));
172
173     // Handle the case where there is a typing style.
174     // FIXME: Improve typing style.
175     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
176     CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
177     if (typingStyle && typingStyle->length() > 0)
178         applyStyle(typingStyle);
179
180     if (!selectInsertedText)
181         setEndingSelection(endingSelection().end(), endingSelection().endAffinity());
182 }
183
184 DOM::Position InsertTextCommand::insertTab(Position pos)
185 {
186     Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent();
187     NodeImpl *node = insertPos.node();
188     unsigned int offset = insertPos.offset();
189
190 //#ifdef COALESCE_TAB_SPANS
191 #if 1
192     // keep tabs coalesced in tab span
193     if (isTabSpanTextNode(node)) {
194         insertTextIntoNode(static_cast<TextImpl *>(node), offset, "\t");
195         return Position(node, offset + 1);
196     }
197 #else
198     if (isTabSpanTextNode(node)) {
199         node = node->parentNode();
200         if (offset > (unsigned int) node->caretMinOffset())
201             insertPos = Position(node->parentNode(), node->nodeIndex() + 1);
202         else
203             insertPos = Position(node->parentNode(), node->nodeIndex());
204         node = insertPos.node();
205         offset = insertPos.offset();
206     }
207 #endif
208     
209     // create new tab span
210     DOM::ElementImpl * spanNode = createTabSpanElement(document());
211     
212     // place it
213     if (!node->isTextNode()) {
214         insertNodeAt(spanNode, node, offset);
215     } else {
216         TextImpl *textNode = static_cast<TextImpl *>(node);
217         if (offset >= textNode->length()) {
218             insertNodeAfter(spanNode, textNode);
219         } else {
220             // split node to make room for the span
221             // NOTE: splitTextNode uses textNode for the
222             // second node in the split, so we need to
223             // insert the span before it.
224             if (offset > 0)
225                 splitTextNode(textNode, offset);
226             insertNodeBefore(spanNode, textNode);
227         }
228     }
229     
230     // return the position following the new tab
231     return Position(spanNode->lastChild(), spanNode->lastChild()->caretMaxOffset());
232 }
233
234 bool InsertTextCommand::isInsertTextCommand() const
235 {
236     return true;
237 }
238
239 } // namespace khtml