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