Home | History | Annotate | Download | only in editing
      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 
     39 namespace WebCore {
     40 
     41 InsertTextCommand::InsertTextCommand(Document *document)
     42     : CompositeEditCommand(document)
     43 {
     44 }
     45 
     46 void InsertTextCommand::doApply()
     47 {
     48 }
     49 
     50 Position 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.
     72 bool 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 
    105 void 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 
    205 Position 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 
    243 bool InsertTextCommand::isInsertTextCommand() const
    244 {
    245     return true;
    246 }
    247 
    248 }
    249