| OLD | NEW |
| (Empty) |
| 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 "core/editing/BreakBlockquoteCommand.h" | |
| 28 | |
| 29 #include "core/HTMLNames.h" | |
| 30 #include "core/dom/NodeTraversal.h" | |
| 31 #include "core/dom/Text.h" | |
| 32 #include "core/editing/EditingUtilities.h" | |
| 33 #include "core/editing/VisiblePosition.h" | |
| 34 #include "core/html/HTMLBRElement.h" | |
| 35 #include "core/html/HTMLElement.h" | |
| 36 #include "core/html/HTMLQuoteElement.h" | |
| 37 #include "core/layout/LayoutListItem.h" | |
| 38 | |
| 39 namespace blink { | |
| 40 | |
| 41 using namespace HTMLNames; | |
| 42 | |
| 43 namespace { | |
| 44 | |
| 45 bool isFirstVisiblePositionInNode(const VisiblePosition& visiblePosition, const
ContainerNode* node) | |
| 46 { | |
| 47 if (visiblePosition.isNull()) | |
| 48 return false; | |
| 49 | |
| 50 if (!visiblePosition.deepEquivalent().computeContainerNode()->isDescendantOf
(node)) | |
| 51 return false; | |
| 52 | |
| 53 VisiblePosition previous = visiblePosition.previous(); | |
| 54 return previous.isNull() || !previous.deepEquivalent().anchorNode()->isDesce
ndantOf(node); | |
| 55 } | |
| 56 | |
| 57 bool isLastVisiblePositionInNode(const VisiblePosition& visiblePosition, const C
ontainerNode* node) | |
| 58 { | |
| 59 if (visiblePosition.isNull()) | |
| 60 return false; | |
| 61 | |
| 62 if (!visiblePosition.deepEquivalent().computeContainerNode()->isDescendantOf
(node)) | |
| 63 return false; | |
| 64 | |
| 65 VisiblePosition next = visiblePosition.next(); | |
| 66 return next.isNull() || !next.deepEquivalent().anchorNode()->isDescendantOf(
node); | |
| 67 } | |
| 68 | |
| 69 } // namespace | |
| 70 | |
| 71 BreakBlockquoteCommand::BreakBlockquoteCommand(Document& document) | |
| 72 : CompositeEditCommand(document) | |
| 73 { | |
| 74 } | |
| 75 | |
| 76 void BreakBlockquoteCommand::doApply() | |
| 77 { | |
| 78 if (endingSelection().isNone()) | |
| 79 return; | |
| 80 | |
| 81 // Delete the current selection. | |
| 82 if (endingSelection().isRange()) | |
| 83 deleteSelection(false, false); | |
| 84 | |
| 85 // This is a scenario that should never happen, but we want to | |
| 86 // make sure we don't dereference a null pointer below. | |
| 87 | |
| 88 ASSERT(!endingSelection().isNone()); | |
| 89 | |
| 90 if (endingSelection().isNone()) | |
| 91 return; | |
| 92 | |
| 93 VisiblePosition visiblePos = endingSelection().visibleStart(); | |
| 94 | |
| 95 // pos is a position equivalent to the caret. We use downstream() so that p
os will | |
| 96 // be in the first node that we need to move (there are a few exceptions to
this, see below). | |
| 97 Position pos = endingSelection().start().downstream(); | |
| 98 | |
| 99 // Find the top-most blockquote from the start. | |
| 100 HTMLQuoteElement* topBlockquote = toHTMLQuoteElement(highestEnclosingNodeOfT
ype(pos, isMailHTMLBlockquoteElement)); | |
| 101 if (!topBlockquote || !topBlockquote->parentNode()) | |
| 102 return; | |
| 103 | |
| 104 RefPtrWillBeRawPtr<HTMLBRElement> breakElement = createBreakElement(document
()); | |
| 105 | |
| 106 bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockqu
ote); | |
| 107 | |
| 108 // If the position is at the beginning of the top quoted content, we don't n
eed to break the quote. | |
| 109 // Instead, insert the break before the blockquote, unless the position is a
s the end of the the quoted content. | |
| 110 if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPos
InNode) { | |
| 111 insertNodeBefore(breakElement.get(), topBlockquote); | |
| 112 setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get(
)), DOWNSTREAM, endingSelection().isDirectional())); | |
| 113 rebalanceWhitespace(); | |
| 114 return; | |
| 115 } | |
| 116 | |
| 117 // Insert a break after the top blockquote. | |
| 118 insertNodeAfter(breakElement.get(), topBlockquote); | |
| 119 | |
| 120 // If we're inserting the break at the end of the quoted content, we don't n
eed to break the quote. | |
| 121 if (isLastVisPosInNode) { | |
| 122 setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get(
)), DOWNSTREAM, endingSelection().isDirectional())); | |
| 123 rebalanceWhitespace(); | |
| 124 return; | |
| 125 } | |
| 126 | |
| 127 // Don't move a line break just after the caret. Doing so would create an e
xtra, empty paragraph | |
| 128 // in the new blockquote. | |
| 129 if (lineBreakExistsAtVisiblePosition(visiblePos)) | |
| 130 pos = pos.next(); | |
| 131 | |
| 132 // Adjust the position so we don't split at the beginning of a quote. | |
| 133 while (isFirstVisiblePositionInNode(VisiblePosition(pos), toHTMLQuoteElement
(enclosingNodeOfType(pos, isMailHTMLBlockquoteElement)))) | |
| 134 pos = pos.previous(); | |
| 135 | |
| 136 // startNode is the first node that we need to move to the new blockquote. | |
| 137 Node* startNode = pos.anchorNode(); | |
| 138 ASSERT(startNode); | |
| 139 | |
| 140 // Split at pos if in the middle of a text node. | |
| 141 if (startNode->isTextNode()) { | |
| 142 Text* textNode = toText(startNode); | |
| 143 int textOffset = pos.computeOffsetInContainerNode(); | |
| 144 if ((unsigned)textOffset >= textNode->length()) { | |
| 145 startNode = NodeTraversal::next(*startNode); | |
| 146 ASSERT(startNode); | |
| 147 } else if (textOffset > 0) { | |
| 148 splitTextNode(textNode, textOffset); | |
| 149 } | |
| 150 } else if (pos.computeEditingOffset() > 0) { | |
| 151 Node* childAtOffset = NodeTraversal::childAt(*startNode, pos.computeEdit
ingOffset()); | |
| 152 startNode = childAtOffset ? childAtOffset : NodeTraversal::next(*startNo
de); | |
| 153 ASSERT(startNode); | |
| 154 } | |
| 155 | |
| 156 // If there's nothing inside topBlockquote to move, we're finished. | |
| 157 if (!startNode->isDescendantOf(topBlockquote)) { | |
| 158 setEndingSelection(VisibleSelection(VisiblePosition(firstPositionInOrBef
oreNode(startNode)), endingSelection().isDirectional())); | |
| 159 return; | |
| 160 } | |
| 161 | |
| 162 // Build up list of ancestors in between the start node and the top blockquo
te. | |
| 163 WillBeHeapVector<RefPtrWillBeMember<Element>> ancestors; | |
| 164 for (Element* node = startNode->parentElement(); node && node != topBlockquo
te; node = node->parentElement()) | |
| 165 ancestors.append(node); | |
| 166 | |
| 167 // Insert a clone of the top blockquote after the break. | |
| 168 RefPtrWillBeRawPtr<Element> clonedBlockquote = topBlockquote->cloneElementWi
thoutChildren(); | |
| 169 insertNodeAfter(clonedBlockquote.get(), breakElement.get()); | |
| 170 | |
| 171 // Clone startNode's ancestors into the cloned blockquote. | |
| 172 // On exiting this loop, clonedAncestor is the lowest ancestor | |
| 173 // that was cloned (i.e. the clone of either ancestors.last() | |
| 174 // or clonedBlockquote if ancestors is empty). | |
| 175 RefPtrWillBeRawPtr<Element> clonedAncestor = clonedBlockquote; | |
| 176 for (size_t i = ancestors.size(); i != 0; --i) { | |
| 177 RefPtrWillBeRawPtr<Element> clonedChild = ancestors[i - 1]->cloneElement
WithoutChildren(); | |
| 178 // Preserve list item numbering in cloned lists. | |
| 179 if (isHTMLOListElement(*clonedChild)) { | |
| 180 Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode; | |
| 181 // The first child of the cloned list might not be a list item eleme
nt, | |
| 182 // find the first one so that we know where to start numbering. | |
| 183 while (listChildNode && !isHTMLLIElement(*listChildNode)) | |
| 184 listChildNode = listChildNode->nextSibling(); | |
| 185 if (isListItem(listChildNode)) | |
| 186 setNodeAttribute(clonedChild, startAttr, AtomicString::number(to
LayoutListItem(listChildNode->layoutObject())->value())); | |
| 187 } | |
| 188 | |
| 189 appendNode(clonedChild.get(), clonedAncestor.get()); | |
| 190 clonedAncestor = clonedChild; | |
| 191 } | |
| 192 | |
| 193 moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor); | |
| 194 | |
| 195 if (!ancestors.isEmpty()) { | |
| 196 // Split the tree up the ancestor chain until the topBlockquote | |
| 197 // Throughout this loop, clonedParent is the clone of ancestor's parent. | |
| 198 // This is so we can clone ancestor's siblings and place the clones | |
| 199 // into the clone corresponding to the ancestor's parent. | |
| 200 RefPtrWillBeRawPtr<Element> ancestor = nullptr; | |
| 201 RefPtrWillBeRawPtr<Element> clonedParent = nullptr; | |
| 202 for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parent
Element(); | |
| 203 ancestor && ancestor != topBlockquote; | |
| 204 ancestor = ancestor->parentElement(), clonedParent = clonedParent->p
arentElement()) | |
| 205 moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedP
arent); | |
| 206 | |
| 207 // If the startNode's original parent is now empty, remove it | |
| 208 Element* originalParent = ancestors.first().get(); | |
| 209 if (!originalParent->hasChildren()) | |
| 210 removeNode(originalParent); | |
| 211 } | |
| 212 | |
| 213 // Make sure the cloned block quote renders. | |
| 214 addBlockPlaceholderIfNeeded(clonedBlockquote.get()); | |
| 215 | |
| 216 // Put the selection right before the break. | |
| 217 setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()),
DOWNSTREAM, endingSelection().isDirectional())); | |
| 218 rebalanceWhitespace(); | |
| 219 } | |
| 220 | |
| 221 } // namespace blink | |
| OLD | NEW |