5de48d96d5ec793155396a295905833f90ccd57d
[WebKit-https.git] / 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 "CharacterNames.h"
30 #include "CSSMutableStyleDeclaration.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "Document.h"
33 #include "Element.h"
34 #include "EditingText.h"
35 #include "Editor.h"
36 #include "Frame.h"
37 #include "Logging.h"
38 #include "HTMLInterchange.h"
39 #include "htmlediting.h"
40 #include "TextIterator.h"
41 #include "TypingCommand.h"
42 #include "visible_units.h"
43
44 namespace WebCore {
45
46 InsertTextCommand::InsertTextCommand(Document *document) 
47     : CompositeEditCommand(document), m_charactersAdded(0)
48 {
49 }
50
51 void InsertTextCommand::doApply()
52 {
53 }
54
55 Position InsertTextCommand::prepareForTextInsertion(const Position& p)
56 {
57     Position pos = p;
58     // If an anchor was removed and the selection hasn't changed, we restore it.
59     RefPtr<Node> anchor = document()->frame()->editor()->removedAnchor();
60     if (anchor) {
61         insertNodeAt(anchor.get(), pos);
62         document()->frame()->editor()->setRemovedAnchor(0);
63         pos = Position(anchor.get(), 0);
64     }
65     // Prepare for text input by looking at the specified position.
66     // It may be necessary to insert a text node to receive characters.
67     if (!pos.node()->isTextNode()) {
68         RefPtr<Node> textNode = document()->createEditingTextNode("");
69         insertNodeAt(textNode.get(), pos);
70         return Position(textNode.get(), 0);
71     }
72
73     if (isTabSpanTextNode(pos.node())) {
74         RefPtr<Node> textNode = document()->createEditingTextNode("");
75         insertNodeAtTabSpanPosition(textNode.get(), pos);
76         return Position(textNode.get(), 0);
77     }
78
79     return pos;
80 }
81
82 void InsertTextCommand::input(const String& originalText, bool selectInsertedText)
83 {
84     String text = originalText;
85     
86     ASSERT(text.find('\n') == -1);
87
88     if (endingSelection().isNone())
89         return;
90         
91     if (RenderObject* renderer = endingSelection().start().node()->renderer())
92         if (renderer->style()->collapseWhiteSpace())
93             // Turn all spaces into non breaking spaces, to make sure that they are treated
94             // literally, and aren't collapsed after insertion. They will be rebalanced 
95             // (turned into a sequence of regular and non breaking spaces) below.
96             text.replace(' ', noBreakSpace);
97     
98     // Delete the current selection.
99     // FIXME: This delete operation blows away the typing style.
100     if (endingSelection().isRange())
101         deleteSelection(false, true, true, false);
102     
103     // Insert the character at the leftmost candidate.
104     Position startPosition = endingSelection().start().upstream();
105     // It is possible for the node that contains startPosition to contain only unrendered whitespace,
106     // and so deleteInsignificantText could remove it.  Save the position before the node in case that happens.
107     Position positionBeforeStartNode(positionBeforeNode(startPosition.node()));
108     deleteInsignificantText(startPosition.upstream(), startPosition.downstream());
109     if (!startPosition.node()->inDocument())
110         startPosition = positionBeforeStartNode;
111     if (!startPosition.isCandidate())
112         startPosition = startPosition.downstream();
113     
114     // FIXME: This typing around anchor behavior doesn't exactly match TextEdit.  In TextEdit,
115     // you won't be placed inside a link when typing after it if you've just placed the caret
116     // there with the mouse.
117     startPosition = positionAvoidingSpecialElementBoundary(startPosition, false);
118     
119     Position endPosition;
120     
121     if (text == "\t") {
122         endPosition = insertTab(startPosition);
123         startPosition = endPosition.previous();
124         removePlaceholderAt(VisiblePosition(startPosition));
125         m_charactersAdded += 1;
126     } else {
127         // Make sure the document is set up to receive text
128         startPosition = prepareForTextInsertion(startPosition);
129         removePlaceholderAt(VisiblePosition(startPosition));
130         Text *textNode = static_cast<Text *>(startPosition.node());
131         int offset = startPosition.offset();
132
133         insertTextIntoNode(textNode, offset, text);
134         endPosition = Position(textNode, offset + text.length());
135
136         // The insertion may require adjusting adjacent whitespace, if it is present.
137         rebalanceWhitespaceAt(endPosition);
138         // Rebalancing on both sides isn't necessary if we've inserted a space.
139         if (originalText != " ") 
140             rebalanceWhitespaceAt(startPosition);
141             
142         m_charactersAdded += text.length();
143     }
144
145     setEndingSelection(Selection(startPosition, endPosition, DOWNSTREAM));
146
147     // Handle the case where there is a typing style.
148     // FIXME: Improve typing style.
149     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
150     CSSMutableStyleDeclaration* typingStyle = document()->frame()->typingStyle();
151     RefPtr<CSSComputedStyleDeclaration> endingStyle = endPosition.computedStyle();
152     endingStyle->diff(typingStyle);
153     if (typingStyle && typingStyle->length() > 0)
154         applyStyle(typingStyle);
155
156     if (!selectInsertedText)
157         setEndingSelection(Selection(endingSelection().end(), endingSelection().affinity()));
158 }
159
160 Position InsertTextCommand::insertTab(const Position& pos)
161 {
162     Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent();
163         
164     Node *node = insertPos.node();
165     unsigned int offset = insertPos.offset();
166
167     // keep tabs coalesced in tab span
168     if (isTabSpanTextNode(node)) {
169         insertTextIntoNode(static_cast<Text *>(node), offset, "\t");
170         return Position(node, offset + 1);
171     }
172     
173     // create new tab span
174     RefPtr<Element> spanNode = createTabSpanElement(document());
175     
176     // place it
177     if (!node->isTextNode()) {
178         insertNodeAt(spanNode.get(), insertPos);
179     } else {
180         Text *textNode = static_cast<Text *>(node);
181         if (offset >= textNode->length()) {
182             insertNodeAfter(spanNode.get(), textNode);
183         } else {
184             // split node to make room for the span
185             // NOTE: splitTextNode uses textNode for the
186             // second node in the split, so we need to
187             // insert the span before it.
188             if (offset > 0)
189                 splitTextNode(textNode, offset);
190             insertNodeBefore(spanNode.get(), textNode);
191         }
192     }
193     
194     // return the position following the new tab
195     return Position(spanNode->lastChild(), caretMaxOffset(spanNode->lastChild()));
196 }
197
198 bool InsertTextCommand::isInsertTextCommand() const
199 {
200     return true;
201 }
202
203 }