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