BreakBlockquoteCommand.cpp revision cad810f21b803229eb11403f9209855525a25d57
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 "BreakBlockquoteCommand.h" 28 29#include "HTMLElement.h" 30#include "HTMLNames.h" 31#include "RenderListItem.h" 32#include "Text.h" 33#include "VisiblePosition.h" 34#include "htmlediting.h" 35 36namespace WebCore { 37 38using namespace HTMLNames; 39 40BreakBlockquoteCommand::BreakBlockquoteCommand(Document *document) 41 : CompositeEditCommand(document) 42{ 43} 44 45void BreakBlockquoteCommand::doApply() 46{ 47 if (endingSelection().isNone()) 48 return; 49 50 // Delete the current selection. 51 if (endingSelection().isRange()) 52 deleteSelection(false, false); 53 54 // This is a scenario that should never happen, but we want to 55 // make sure we don't dereference a null pointer below. 56 57 ASSERT(!endingSelection().isNone()); 58 59 if (endingSelection().isNone()) 60 return; 61 62 VisiblePosition visiblePos = endingSelection().visibleStart(); 63 64 // pos is a position equivalent to the caret. We use downstream() so that pos will 65 // be in the first node that we need to move (there are a few exceptions to this, see below). 66 Position pos = endingSelection().start().downstream(); 67 68 // Find the top-most blockquote from the start. 69 Element* topBlockquote = 0; 70 for (ContainerNode* node = pos.node()->parentNode(); node; node = node->parentNode()) { 71 if (isMailBlockquote(node)) 72 topBlockquote = static_cast<Element*>(node); 73 } 74 if (!topBlockquote || !topBlockquote->parentNode()) 75 return; 76 77 RefPtr<Element> breakNode = createBreakElement(document()); 78 79 bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote); 80 81 // If the position is at the beginning of the top quoted content, we don't need to break the quote. 82 // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content. 83 if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) { 84 insertNodeBefore(breakNode.get(), topBlockquote); 85 setEndingSelection(VisibleSelection(Position(breakNode.get(), 0), DOWNSTREAM)); 86 rebalanceWhitespace(); 87 return; 88 } 89 90 // Insert a break after the top blockquote. 91 insertNodeAfter(breakNode.get(), topBlockquote); 92 93 // If we're inserting the break at the end of the quoted content, we don't need to break the quote. 94 if (isLastVisPosInNode) { 95 setEndingSelection(VisibleSelection(Position(breakNode.get(), 0), DOWNSTREAM)); 96 rebalanceWhitespace(); 97 return; 98 } 99 100 // Don't move a line break just after the caret. Doing so would create an extra, empty paragraph 101 // in the new blockquote. 102 if (lineBreakExistsAtVisiblePosition(visiblePos)) 103 pos = pos.next(); 104 105 // Adjust the position so we don't split at the beginning of a quote. 106 while (isFirstVisiblePositionInNode(VisiblePosition(pos), nearestMailBlockquote(pos.node()))) 107 pos = pos.previous(); 108 109 // startNode is the first node that we need to move to the new blockquote. 110 Node* startNode = pos.node(); 111 112 // Split at pos if in the middle of a text node. 113 if (startNode->isTextNode()) { 114 Text* textNode = static_cast<Text*>(startNode); 115 if ((unsigned)pos.deprecatedEditingOffset() >= textNode->length()) { 116 startNode = startNode->traverseNextNode(); 117 ASSERT(startNode); 118 } else if (pos.deprecatedEditingOffset() > 0) 119 splitTextNode(textNode, pos.deprecatedEditingOffset()); 120 } else if (pos.deprecatedEditingOffset() > 0) { 121 Node* childAtOffset = startNode->childNode(pos.deprecatedEditingOffset()); 122 startNode = childAtOffset ? childAtOffset : startNode->traverseNextNode(); 123 ASSERT(startNode); 124 } 125 126 // If there's nothing inside topBlockquote to move, we're finished. 127 if (!startNode->isDescendantOf(topBlockquote)) { 128 setEndingSelection(VisibleSelection(VisiblePosition(Position(startNode, 0)))); 129 return; 130 } 131 132 // Build up list of ancestors in between the start node and the top blockquote. 133 Vector<Element*> ancestors; 134 for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement()) 135 ancestors.append(node); 136 137 // Insert a clone of the top blockquote after the break. 138 RefPtr<Element> clonedBlockquote = topBlockquote->cloneElementWithoutChildren(); 139 insertNodeAfter(clonedBlockquote.get(), breakNode.get()); 140 141 // Clone startNode's ancestors into the cloned blockquote. 142 // On exiting this loop, clonedAncestor is the lowest ancestor 143 // that was cloned (i.e. the clone of either ancestors.last() 144 // or clonedBlockquote if ancestors is empty). 145 RefPtr<Element> clonedAncestor = clonedBlockquote; 146 for (size_t i = ancestors.size(); i != 0; --i) { 147 RefPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); 148 // Preserve list item numbering in cloned lists. 149 if (clonedChild->isElementNode() && clonedChild->hasTagName(olTag)) { 150 Node* listChildNode = i > 1 ? ancestors[i - 2] : startNode; 151 // The first child of the cloned list might not be a list item element, 152 // find the first one so that we know where to start numbering. 153 while (listChildNode && !listChildNode->hasTagName(liTag)) 154 listChildNode = listChildNode->nextSibling(); 155 if (listChildNode && listChildNode->renderer()) 156 setNodeAttribute(static_cast<Element*>(clonedChild.get()), startAttr, String::number(toRenderListItem(listChildNode->renderer())->value())); 157 } 158 159 appendNode(clonedChild.get(), clonedAncestor.get()); 160 clonedAncestor = clonedChild; 161 } 162 163 // Move the startNode and its siblings. 164 Node *moveNode = startNode; 165 while (moveNode) { 166 Node *next = moveNode->nextSibling(); 167 removeNode(moveNode); 168 appendNode(moveNode, clonedAncestor.get()); 169 moveNode = next; 170 } 171 172 if (!ancestors.isEmpty()) { 173 // Split the tree up the ancestor chain until the topBlockquote 174 // Throughout this loop, clonedParent is the clone of ancestor's parent. 175 // This is so we can clone ancestor's siblings and place the clones 176 // into the clone corresponding to the ancestor's parent. 177 Element* ancestor; 178 Element* clonedParent; 179 for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement(); 180 ancestor && ancestor != topBlockquote; 181 ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement()) { 182 moveNode = ancestor->nextSibling(); 183 while (moveNode) { 184 Node *next = moveNode->nextSibling(); 185 removeNode(moveNode); 186 appendNode(moveNode, clonedParent); 187 moveNode = next; 188 } 189 } 190 191 // If the startNode's original parent is now empty, remove it 192 Node* originalParent = ancestors.first(); 193 if (!originalParent->hasChildNodes()) 194 removeNode(originalParent); 195 } 196 197 // Make sure the cloned block quote renders. 198 addBlockPlaceholderIfNeeded(clonedBlockquote.get()); 199 200 // Put the selection right before the break. 201 setEndingSelection(VisibleSelection(Position(breakNode.get(), 0), DOWNSTREAM)); 202 rebalanceWhitespace(); 203} 204 205} // namespace WebCore 206