WebCore/editing/InsertParagraphSeparatorCommand.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 /*
       
     2  * Copyright (C) 2005, 2006 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 "InsertParagraphSeparatorCommand.h"
       
    28 
       
    29 #include "CSSComputedStyleDeclaration.h"
       
    30 #include "CSSMutableStyleDeclaration.h"
       
    31 #include "CSSPropertyNames.h"
       
    32 #include "Document.h"
       
    33 #include "HTMLElement.h"
       
    34 #include "HTMLNames.h"
       
    35 #include "InsertLineBreakCommand.h"
       
    36 #include "Logging.h"
       
    37 #include "RenderObject.h"
       
    38 #include "Text.h"
       
    39 #include "htmlediting.h"
       
    40 #include "visible_units.h"
       
    41 #include "ApplyStyleCommand.h"
       
    42 
       
    43 namespace WebCore {
       
    44 
       
    45 using namespace HTMLNames;
       
    46 
       
    47 // When inserting a new line, we want to avoid nesting empty divs if we can.  Otherwise, when
       
    48 // pasting, it's easy to have each new line be a div deeper than the previous.  E.g., in the case
       
    49 // below, we want to insert at ^ instead of |.
       
    50 // <div>foo<div>bar</div>|</div>^
       
    51 static Element* highestVisuallyEquivalentDivBelowRoot(Element* startBlock)
       
    52 {
       
    53     Element* curBlock = startBlock;
       
    54     // We don't want to return a root node (if it happens to be a div, e.g., in a document fragment) because there are no
       
    55     // siblings for us to append to.
       
    56     while (!curBlock->nextSibling() && curBlock->parentElement()->hasTagName(divTag) && curBlock->parentElement()->parentElement()) {
       
    57         NamedNodeMap* attributes = curBlock->parentElement()->attributes(true);
       
    58         if (attributes && !attributes->isEmpty())
       
    59             break;
       
    60         curBlock = curBlock->parentElement();
       
    61     }
       
    62     return curBlock;
       
    63 }
       
    64 
       
    65 InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(Document *document, bool mustUseDefaultParagraphElement) 
       
    66     : CompositeEditCommand(document)
       
    67     , m_mustUseDefaultParagraphElement(mustUseDefaultParagraphElement)
       
    68 {
       
    69 }
       
    70 
       
    71 bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
       
    72 {
       
    73     return true;
       
    74 }
       
    75 
       
    76 void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
       
    77 {
       
    78     // It is only important to set a style to apply later if we're at the boundaries of
       
    79     // a paragraph. Otherwise, content that is moved as part of the work of the command
       
    80     // will lend their styles to the new paragraph without any extra work needed.
       
    81     VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
       
    82     if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos))
       
    83         return;
       
    84     
       
    85     m_style = ApplyStyleCommand::editingStyleAtPosition(pos, IncludeTypingStyle);
       
    86 }
       
    87 
       
    88 void InsertParagraphSeparatorCommand::applyStyleAfterInsertion(Node* originalEnclosingBlock)
       
    89 {
       
    90     // Not only do we break out of header tags, but we also do not preserve the typing style,
       
    91     // in order to match other browsers.
       
    92     if (originalEnclosingBlock->hasTagName(h1Tag) ||
       
    93         originalEnclosingBlock->hasTagName(h2Tag) ||
       
    94         originalEnclosingBlock->hasTagName(h3Tag) ||
       
    95         originalEnclosingBlock->hasTagName(h4Tag) ||
       
    96         originalEnclosingBlock->hasTagName(h5Tag))
       
    97         return;
       
    98         
       
    99     if (!m_style)
       
   100         return;
       
   101     
       
   102     prepareEditingStyleToApplyAt(m_style.get(), endingSelection().start());
       
   103 
       
   104     if (m_style->length() > 0)
       
   105         applyStyle(m_style.get());
       
   106 }
       
   107 
       
   108 bool InsertParagraphSeparatorCommand::shouldUseDefaultParagraphElement(Node* enclosingBlock) const
       
   109 {
       
   110     if (m_mustUseDefaultParagraphElement)
       
   111         return true;
       
   112     
       
   113     // Assumes that if there was a range selection, it was already deleted.
       
   114     if (!isEndOfBlock(endingSelection().visibleStart()))
       
   115         return false;
       
   116 
       
   117     return enclosingBlock->hasTagName(h1Tag) ||
       
   118            enclosingBlock->hasTagName(h2Tag) ||
       
   119            enclosingBlock->hasTagName(h3Tag) ||
       
   120            enclosingBlock->hasTagName(h4Tag) ||
       
   121            enclosingBlock->hasTagName(h5Tag);
       
   122 }
       
   123 
       
   124 void InsertParagraphSeparatorCommand::getAncestorsInsideBlock(const Node* insertionNode, Element* outerBlock, Vector<Element*>& ancestors)
       
   125 {
       
   126     ancestors.clear();
       
   127     
       
   128     // Build up list of ancestors elements between the insertion node and the outer block.
       
   129     if (insertionNode != outerBlock) {
       
   130         for (Element* n = insertionNode->parentElement(); n && n != outerBlock; n = n->parentElement())
       
   131             ancestors.append(n);
       
   132     }
       
   133 }
       
   134 
       
   135 PassRefPtr<Element> InsertParagraphSeparatorCommand::cloneHierarchyUnderNewBlock(const Vector<Element*>& ancestors, PassRefPtr<Element> blockToInsert)
       
   136 {
       
   137     // Make clones of ancestors in between the start node and the start block.
       
   138     RefPtr<Element> parent = blockToInsert;
       
   139     for (size_t i = ancestors.size(); i != 0; --i) {
       
   140         RefPtr<Element> child = ancestors[i - 1]->cloneElementWithoutChildren();
       
   141         appendNode(child, parent);
       
   142         parent = child.release();
       
   143     }
       
   144     
       
   145     return parent.release();
       
   146 }
       
   147 
       
   148 void InsertParagraphSeparatorCommand::doApply()
       
   149 {
       
   150     bool splitText = false;
       
   151     if (endingSelection().isNone())
       
   152         return;
       
   153     
       
   154     Position insertionPosition = endingSelection().start();
       
   155         
       
   156     EAffinity affinity = endingSelection().affinity();
       
   157         
       
   158     // Delete the current selection.
       
   159     if (endingSelection().isRange()) {
       
   160         calculateStyleBeforeInsertion(insertionPosition);
       
   161         deleteSelection(false, true);
       
   162         insertionPosition = endingSelection().start();
       
   163         affinity = endingSelection().affinity();
       
   164     }
       
   165     
       
   166     // FIXME: The rangeCompliantEquivalent conversion needs to be moved into enclosingBlock.
       
   167     Node* startBlockNode = enclosingBlock(rangeCompliantEquivalent(insertionPosition).node());
       
   168     Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent();
       
   169     Element* startBlock = static_cast<Element*>(startBlockNode);
       
   170     if (!startBlockNode
       
   171             || !startBlockNode->isElementNode()
       
   172             || !startBlock->parentNode()
       
   173             || isTableCell(startBlock)
       
   174             || startBlock->hasTagName(formTag)
       
   175             // FIXME: If the node is hidden, we don't have a canonical position so we will do the wrong thing for tables and <hr>. https://bugs.webkit.org/show_bug.cgi?id=40342
       
   176             || (!canonicalPos.isNull() && canonicalPos.node()->renderer() && canonicalPos.node()->renderer()->isTable())
       
   177             || (!canonicalPos.isNull() && canonicalPos.node()->hasTagName(hrTag))) {
       
   178         applyCommandToComposite(InsertLineBreakCommand::create(document()));
       
   179         return;
       
   180     }
       
   181     
       
   182     // Use the leftmost candidate.
       
   183     insertionPosition = insertionPosition.upstream();
       
   184     if (!insertionPosition.isCandidate())
       
   185         insertionPosition = insertionPosition.downstream();
       
   186 
       
   187     // Adjust the insertion position after the delete
       
   188     insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition);
       
   189     VisiblePosition visiblePos(insertionPosition, affinity);
       
   190     calculateStyleBeforeInsertion(insertionPosition);
       
   191 
       
   192     //---------------------------------------------------------------------
       
   193     // Handle special case of typing return on an empty list item
       
   194     if (breakOutOfEmptyListItem())
       
   195         return;
       
   196 
       
   197     //---------------------------------------------------------------------
       
   198     // Prepare for more general cases.
       
   199 
       
   200     bool isFirstInBlock = isStartOfBlock(visiblePos);
       
   201     bool isLastInBlock = isEndOfBlock(visiblePos);
       
   202     bool nestNewBlock = false;
       
   203 
       
   204     // Create block to be inserted.
       
   205     RefPtr<Element> blockToInsert;
       
   206     if (startBlock == startBlock->rootEditableElement()) {
       
   207         blockToInsert = createDefaultParagraphElement(document());
       
   208         nestNewBlock = true;
       
   209     } else if (shouldUseDefaultParagraphElement(startBlock)) 
       
   210         blockToInsert = createDefaultParagraphElement(document());
       
   211     else
       
   212         blockToInsert = startBlock->cloneElementWithoutChildren();
       
   213 
       
   214     //---------------------------------------------------------------------
       
   215     // Handle case when position is in the last visible position in its block,
       
   216     // including when the block is empty. 
       
   217     if (isLastInBlock) {
       
   218         if (nestNewBlock) {
       
   219             if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) {
       
   220                 // The block is empty.  Create an empty block to
       
   221                 // represent the paragraph that we're leaving.
       
   222                 RefPtr<Element> extraBlock = createDefaultParagraphElement(document());
       
   223                 appendNode(extraBlock, startBlock);
       
   224                 appendBlockPlaceholder(extraBlock);
       
   225             }
       
   226             appendNode(blockToInsert, startBlock);
       
   227         } else {
       
   228             // We can get here if we pasted a copied portion of a blockquote with a newline at the end and are trying to paste it
       
   229             // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted.
       
   230             if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote))
       
   231                 startBlock = static_cast<Element*>(highestBlockquote);
       
   232 
       
   233             // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists).  However,
       
   234             // for div nodes, this can result in nested div tags that are hard to break out of.
       
   235             Element* siblingNode = startBlock;
       
   236             if (blockToInsert->hasTagName(divTag))
       
   237                 siblingNode = highestVisuallyEquivalentDivBelowRoot(startBlock);
       
   238             insertNodeAfter(blockToInsert, siblingNode);
       
   239         }
       
   240 
       
   241         // Recreate the same structure in the new paragraph.
       
   242         
       
   243         Vector<Element*> ancestors;
       
   244         getAncestorsInsideBlock(insertionPosition.node(), startBlock, ancestors);      
       
   245         RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert);
       
   246         
       
   247         appendBlockPlaceholder(parent);
       
   248 
       
   249         setEndingSelection(VisibleSelection(Position(parent.get(), 0), DOWNSTREAM));
       
   250         return;
       
   251     }
       
   252     
       
   253 
       
   254     //---------------------------------------------------------------------
       
   255     // Handle case when position is in the first visible position in its block, and
       
   256     // similar case where previous position is in another, presumeably nested, block.
       
   257     if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) {
       
   258         Node *refNode;
       
   259         if (isFirstInBlock && !nestNewBlock)
       
   260             refNode = startBlock;
       
   261         else if (insertionPosition.node() == startBlock && nestNewBlock) {
       
   262             refNode = startBlock->childNode(insertionPosition.deprecatedEditingOffset());
       
   263             ASSERT(refNode); // must be true or we'd be in the end of block case
       
   264         } else
       
   265             refNode = insertionPosition.node();
       
   266 
       
   267         // find ending selection position easily before inserting the paragraph
       
   268         insertionPosition = insertionPosition.downstream();
       
   269         
       
   270         insertNodeBefore(blockToInsert, refNode);
       
   271 
       
   272         // Recreate the same structure in the new paragraph.
       
   273 
       
   274         Vector<Element*> ancestors;
       
   275         getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(insertionPosition).node(), startBlock, ancestors);
       
   276         
       
   277         appendBlockPlaceholder(cloneHierarchyUnderNewBlock(ancestors, blockToInsert));
       
   278         
       
   279         // In this case, we need to set the new ending selection.
       
   280         setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM));
       
   281         return;
       
   282     }
       
   283 
       
   284     //---------------------------------------------------------------------
       
   285     // Handle the (more complicated) general case,
       
   286 
       
   287     // All of the content in the current block after visiblePos is
       
   288     // about to be wrapped in a new paragraph element.  Add a br before 
       
   289     // it if visiblePos is at the start of a paragraph so that the 
       
   290     // content will move down a line.
       
   291     if (isStartOfParagraph(visiblePos)) {
       
   292         RefPtr<Element> br = createBreakElement(document());
       
   293         insertNodeAt(br.get(), insertionPosition);
       
   294         insertionPosition = positionInParentAfterNode(br.get());
       
   295     }
       
   296     
       
   297     // Move downstream. Typing style code will take care of carrying along the 
       
   298     // style of the upstream position.
       
   299     insertionPosition = insertionPosition.downstream();
       
   300 
       
   301     // At this point, the insertionPosition's node could be a container, and we want to make sure we include
       
   302     // all of the correct nodes when building the ancestor list.  So this needs to be the deepest representation of the position
       
   303     // before we walk the DOM tree.
       
   304     insertionPosition = VisiblePosition(insertionPosition).deepEquivalent();
       
   305 
       
   306     // Build up list of ancestors in between the start node and the start block.
       
   307     Vector<Element*> ancestors;
       
   308     getAncestorsInsideBlock(insertionPosition.node(), startBlock, ancestors);
       
   309 
       
   310     // Make sure we do not cause a rendered space to become unrendered.
       
   311     // FIXME: We need the affinity for pos, but pos.downstream() does not give it
       
   312     Position leadingWhitespace = insertionPosition.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
       
   313     // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions
       
   314     // after the preserved newline, causing the newline to be turned into a nbsp.
       
   315     if (leadingWhitespace.isNotNull() && leadingWhitespace.node()->isTextNode()) {
       
   316         Text* textNode = static_cast<Text*>(leadingWhitespace.node());
       
   317         ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
       
   318         replaceTextInNode(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
       
   319     }
       
   320     
       
   321     // Split at pos if in the middle of a text node.
       
   322     if (insertionPosition.node()->isTextNode()) {
       
   323         Text* textNode = static_cast<Text*>(insertionPosition.node());
       
   324         bool atEnd = (unsigned)insertionPosition.deprecatedEditingOffset() >= textNode->length();
       
   325         if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) {
       
   326             splitTextNode(textNode, insertionPosition.deprecatedEditingOffset());
       
   327             insertionPosition.moveToOffset(0);
       
   328             visiblePos = VisiblePosition(insertionPosition);
       
   329             splitText = true;
       
   330         }
       
   331     }
       
   332 
       
   333     // Put the added block in the tree.
       
   334     if (nestNewBlock)
       
   335         appendNode(blockToInsert.get(), startBlock);
       
   336     else
       
   337         insertNodeAfter(blockToInsert.get(), startBlock);
       
   338 
       
   339     updateLayout();
       
   340     
       
   341     // Make clones of ancestors in between the start node and the outer block.
       
   342     RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert);
       
   343 
       
   344     // If the paragraph separator was inserted at the end of a paragraph, an empty line must be
       
   345     // created.  All of the nodes, starting at visiblePos, are about to be added to the new paragraph 
       
   346     // element.  If the first node to be inserted won't be one that will hold an empty line open, add a br.
       
   347     if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos))
       
   348         appendNode(createBreakElement(document()).get(), blockToInsert.get());
       
   349         
       
   350     // Move the start node and the siblings of the start node.
       
   351     if (insertionPosition.node() != startBlock) {
       
   352         Node* n = insertionPosition.node();
       
   353         if (insertionPosition.deprecatedEditingOffset() >= caretMaxOffset(n))
       
   354             n = n->nextSibling();
       
   355 
       
   356         while (n && n != blockToInsert) {
       
   357             Node *next = n->nextSibling();
       
   358             removeNode(n);
       
   359             appendNode(n, parent.get());
       
   360             n = next;
       
   361         }
       
   362     }            
       
   363 
       
   364     // Move everything after the start node.
       
   365     if (!ancestors.isEmpty()) {
       
   366         Element* leftParent = ancestors.first();
       
   367         while (leftParent && leftParent != startBlock) {
       
   368             parent = parent->parentElement();
       
   369             if (!parent)
       
   370                 break;
       
   371             Node* n = leftParent->nextSibling();
       
   372             while (n && n != blockToInsert) {
       
   373                 Node* next = n->nextSibling();
       
   374                 removeNode(n);
       
   375                 appendNode(n, parent.get());
       
   376                 n = next;
       
   377             }
       
   378             leftParent = leftParent->parentElement();
       
   379         }
       
   380     }
       
   381 
       
   382     // Handle whitespace that occurs after the split
       
   383     if (splitText) {
       
   384         updateLayout();
       
   385         insertionPosition = Position(insertionPosition.node(), 0);
       
   386         if (!insertionPosition.isRenderedCharacter()) {
       
   387             // Clear out all whitespace and insert one non-breaking space
       
   388             ASSERT(!insertionPosition.node()->renderer() || insertionPosition.node()->renderer()->style()->collapseWhiteSpace());
       
   389             deleteInsignificantTextDownstream(insertionPosition);
       
   390             if (insertionPosition.node()->isTextNode())
       
   391                 insertTextIntoNode(static_cast<Text*>(insertionPosition.node()), 0, nonBreakingSpaceString());
       
   392         }
       
   393     }
       
   394 
       
   395     setEndingSelection(VisibleSelection(Position(blockToInsert.get(), 0), DOWNSTREAM));
       
   396     applyStyleAfterInsertion(startBlock);
       
   397 }
       
   398 
       
   399 } // namespace WebCore