| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2006, 2008 Apple 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 (IndentOutdentCommandINCLUDING, BUT NOT L
IMITED 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/IndentOutdentCommand.h" | |
| 28 | |
| 29 #include "core/HTMLNames.h" | |
| 30 #include "core/dom/Document.h" | |
| 31 #include "core/dom/ElementTraversal.h" | |
| 32 #include "core/editing/EditingUtilities.h" | |
| 33 #include "core/editing/InsertListCommand.h" | |
| 34 #include "core/editing/VisibleUnits.h" | |
| 35 #include "core/html/HTMLBRElement.h" | |
| 36 #include "core/html/HTMLElement.h" | |
| 37 #include "core/layout/LayoutObject.h" | |
| 38 | |
| 39 namespace blink { | |
| 40 | |
| 41 using namespace HTMLNames; | |
| 42 | |
| 43 static bool isHTMLListOrBlockquoteElement(const Node* node) | |
| 44 { | |
| 45 if (!node || !node->isHTMLElement()) | |
| 46 return false; | |
| 47 const HTMLElement& element = toHTMLElement(*node); | |
| 48 return isHTMLUListElement(element) || isHTMLOListElement(element) || element
.hasTagName(blockquoteTag); | |
| 49 } | |
| 50 | |
| 51 IndentOutdentCommand::IndentOutdentCommand(Document& document, EIndentType typeO
fAction) | |
| 52 : ApplyBlockElementCommand(document, blockquoteTag, "margin: 0 0 0 40px; bor
der: none; padding: 0px;") | |
| 53 , m_typeOfAction(typeOfAction) | |
| 54 { | |
| 55 } | |
| 56 | |
| 57 bool IndentOutdentCommand::tryIndentingAsListItem(const Position& start, const P
osition& end) | |
| 58 { | |
| 59 // If our selection is not inside a list, bail out. | |
| 60 RefPtrWillBeRawPtr<Node> lastNodeInSelectedParagraph = start.anchorNode(); | |
| 61 RefPtrWillBeRawPtr<HTMLElement> listElement = enclosingList(lastNodeInSelect
edParagraph.get()); | |
| 62 if (!listElement) | |
| 63 return false; | |
| 64 | |
| 65 // Find the block that we want to indent. If it's not a list item (e.g., a
div inside a list item), we bail out. | |
| 66 RefPtrWillBeRawPtr<Element> selectedListItem = enclosingBlock(lastNodeInSele
ctedParagraph.get()); | |
| 67 | |
| 68 // FIXME: we need to deal with the case where there is no li (malformed HTML
) | |
| 69 if (!isHTMLLIElement(selectedListItem)) | |
| 70 return false; | |
| 71 | |
| 72 // FIXME: previousElementSibling does not ignore non-rendered content like <
span></span>. Should we? | |
| 73 RefPtrWillBeRawPtr<Element> previousList = ElementTraversal::previousSibling
(*selectedListItem); | |
| 74 RefPtrWillBeRawPtr<Element> nextList = ElementTraversal::nextSibling(*select
edListItem); | |
| 75 | |
| 76 // We should calculate visible range in list item because inserting new | |
| 77 // list element will change visibility of list item, e.g. :first-child | |
| 78 // CSS selector. | |
| 79 RefPtrWillBeRawPtr<HTMLElement> newList = toHTMLElement(document().createEle
ment(listElement->tagQName(), false).get()); | |
| 80 insertNodeBefore(newList, selectedListItem.get()); | |
| 81 | |
| 82 // We should clone all the children of the list item for indenting purposes.
However, in case the current | |
| 83 // selection does not encompass all its children, we need to explicitally ha
ndle the same. The original | |
| 84 // list item too would require proper deletion in that case. | |
| 85 if (end.anchorNode() == selectedListItem.get() || end.anchorNode()->isDescen
dantOf(selectedListItem->lastChild())) { | |
| 86 moveParagraphWithClones(VisiblePosition(start), VisiblePosition(end), ne
wList.get(), selectedListItem.get()); | |
| 87 } else { | |
| 88 moveParagraphWithClones(VisiblePosition(start), VisiblePosition(position
AfterNode(selectedListItem->lastChild())), newList.get(), selectedListItem.get()
); | |
| 89 removeNode(selectedListItem.get()); | |
| 90 } | |
| 91 | |
| 92 if (canMergeLists(previousList.get(), newList.get())) | |
| 93 mergeIdenticalElements(previousList.get(), newList.get()); | |
| 94 if (canMergeLists(newList.get(), nextList.get())) | |
| 95 mergeIdenticalElements(newList.get(), nextList.get()); | |
| 96 | |
| 97 return true; | |
| 98 } | |
| 99 | |
| 100 void IndentOutdentCommand::indentIntoBlockquote(const Position& start, const Pos
ition& end, RefPtrWillBeRawPtr<HTMLElement>& targetBlockquote) | |
| 101 { | |
| 102 Element* enclosingCell = toElement(enclosingNodeOfType(start, &isTableCell))
; | |
| 103 Element* elementToSplitTo; | |
| 104 if (enclosingCell) | |
| 105 elementToSplitTo = enclosingCell; | |
| 106 else if (enclosingList(start.computeContainerNode())) | |
| 107 elementToSplitTo = enclosingBlock(start.computeContainerNode()); | |
| 108 else | |
| 109 elementToSplitTo = editableRootForPosition(start); | |
| 110 | |
| 111 if (!elementToSplitTo) | |
| 112 return; | |
| 113 | |
| 114 RefPtrWillBeRawPtr<Node> outerBlock = (start.computeContainerNode() == eleme
ntToSplitTo) ? start.computeContainerNode() : splitTreeToNode(start.computeConta
inerNode(), elementToSplitTo).get(); | |
| 115 | |
| 116 VisiblePosition startOfContents(start); | |
| 117 if (!targetBlockquote) { | |
| 118 // Create a new blockquote and insert it as a child of the root editable
element. We accomplish | |
| 119 // this by splitting all parents of the current paragraph up to that poi
nt. | |
| 120 targetBlockquote = createBlockElement(); | |
| 121 if (outerBlock == start.computeContainerNode()) | |
| 122 insertNodeAt(targetBlockquote, start); | |
| 123 else | |
| 124 insertNodeBefore(targetBlockquote, outerBlock); | |
| 125 startOfContents = VisiblePosition(positionInParentAfterNode(*targetBlock
quote)); | |
| 126 } | |
| 127 | |
| 128 VisiblePosition endOfContents(end); | |
| 129 if (startOfContents.isNull() || endOfContents.isNull()) | |
| 130 return; | |
| 131 moveParagraphWithClones(startOfContents, endOfContents, targetBlockquote.get
(), outerBlock.get()); | |
| 132 } | |
| 133 | |
| 134 void IndentOutdentCommand::outdentParagraph() | |
| 135 { | |
| 136 VisiblePosition visibleStartOfParagraph = startOfParagraph(endingSelection()
.visibleStart()); | |
| 137 VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagra
ph); | |
| 138 | |
| 139 HTMLElement* enclosingElement = toHTMLElement(enclosingNodeOfType(visibleSta
rtOfParagraph.deepEquivalent(), &isHTMLListOrBlockquoteElement)); | |
| 140 if (!enclosingElement || !enclosingElement->parentNode()->hasEditableStyle()
) // We can't outdent if there is no place to go! | |
| 141 return; | |
| 142 | |
| 143 // Use InsertListCommand to remove the selection from the list | |
| 144 if (isHTMLOListElement(*enclosingElement)) { | |
| 145 applyCommandToComposite(InsertListCommand::create(document(), InsertList
Command::OrderedList)); | |
| 146 return; | |
| 147 } | |
| 148 if (isHTMLUListElement(*enclosingElement)) { | |
| 149 applyCommandToComposite(InsertListCommand::create(document(), InsertList
Command::UnorderedList)); | |
| 150 return; | |
| 151 } | |
| 152 | |
| 153 // The selection is inside a blockquote i.e. enclosingNode is a blockquote | |
| 154 VisiblePosition positionInEnclosingBlock = VisiblePosition(firstPositionInNo
de(enclosingElement)); | |
| 155 // If the blockquote is inline, the start of the enclosing block coincides w
ith | |
| 156 // positionInEnclosingBlock. | |
| 157 VisiblePosition startOfEnclosingBlock = (enclosingElement->layoutObject() &&
enclosingElement->layoutObject()->isInline()) ? positionInEnclosingBlock : star
tOfBlock(positionInEnclosingBlock); | |
| 158 VisiblePosition lastPositionInEnclosingBlock = VisiblePosition(lastPositionI
nNode(enclosingElement)); | |
| 159 VisiblePosition endOfEnclosingBlock = endOfBlock(lastPositionInEnclosingBloc
k); | |
| 160 if (visibleStartOfParagraph.deepEquivalent() == startOfEnclosingBlock.deepEq
uivalent() | |
| 161 && visibleEndOfParagraph.deepEquivalent() == endOfEnclosingBlock.deepEqu
ivalent()) { | |
| 162 // The blockquote doesn't contain anything outside the paragraph, so it
can be totally removed. | |
| 163 Node* splitPoint = enclosingElement->nextSibling(); | |
| 164 removeNodePreservingChildren(enclosingElement); | |
| 165 // outdentRegion() assumes it is operating on the first paragraph of an
enclosing blockquote, but if there are multiply nested blockquotes and we've | |
| 166 // just removed one, then this assumption isn't true. By splitting the n
ext containing blockquote after this node, we keep this assumption true | |
| 167 if (splitPoint) { | |
| 168 if (Element* splitPointParent = splitPoint->parentElement()) { | |
| 169 if (splitPointParent->hasTagName(blockquoteTag) | |
| 170 && !splitPoint->hasTagName(blockquoteTag) | |
| 171 && splitPointParent->parentNode()->hasEditableStyle()) // We
can't outdent if there is no place to go! | |
| 172 splitElement(splitPointParent, splitPoint); | |
| 173 } | |
| 174 } | |
| 175 | |
| 176 document().updateLayoutIgnorePendingStylesheets(); | |
| 177 visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEq
uivalent()); | |
| 178 visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquiva
lent()); | |
| 179 if (visibleStartOfParagraph.isNotNull() && !isStartOfParagraph(visibleSt
artOfParagraph)) | |
| 180 insertNodeAt(createBreakElement(document()), visibleStartOfParagraph
.deepEquivalent()); | |
| 181 if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfP
aragraph)) | |
| 182 insertNodeAt(createBreakElement(document()), visibleEndOfParagraph.d
eepEquivalent()); | |
| 183 | |
| 184 return; | |
| 185 } | |
| 186 RefPtrWillBeRawPtr<Node> splitBlockquoteNode = enclosingElement; | |
| 187 if (Element* enclosingBlockFlow = enclosingBlock(visibleStartOfParagraph.dee
pEquivalent().anchorNode())) { | |
| 188 if (enclosingBlockFlow != enclosingElement) { | |
| 189 splitBlockquoteNode = splitTreeToNode(enclosingBlockFlow, enclosingE
lement, true); | |
| 190 } else { | |
| 191 // We split the blockquote at where we start outdenting. | |
| 192 Node* highestInlineNode = highestEnclosingNodeOfType(visibleStartOfP
aragraph.deepEquivalent(), isInline, CannotCrossEditingBoundary, enclosingBlockF
low); | |
| 193 splitElement(enclosingElement, highestInlineNode ? highestInlineNode
: visibleStartOfParagraph.deepEquivalent().anchorNode()); | |
| 194 } | |
| 195 } | |
| 196 VisiblePosition startOfParagraphToMove(startOfParagraph(visibleStartOfParagr
aph)); | |
| 197 VisiblePosition endOfParagraphToMove(endOfParagraph(visibleEndOfParagraph)); | |
| 198 if (startOfParagraphToMove.isNull() || endOfParagraphToMove.isNull()) | |
| 199 return; | |
| 200 RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(document(
)); | |
| 201 insertNodeBefore(placeholder, splitBlockquoteNode); | |
| 202 moveParagraph(startOfParagraphToMove, endOfParagraphToMove, VisiblePosition(
positionBeforeNode(placeholder.get())), true); | |
| 203 } | |
| 204 | |
| 205 // FIXME: We should merge this function with ApplyBlockElementCommand::formatSel
ection | |
| 206 void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection
, const VisiblePosition& endOfSelection) | |
| 207 { | |
| 208 VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); | |
| 209 VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection); | |
| 210 | |
| 211 if (endOfCurrentParagraph.deepEquivalent() == endOfLastParagraph.deepEquival
ent()) { | |
| 212 outdentParagraph(); | |
| 213 return; | |
| 214 } | |
| 215 | |
| 216 Position originalSelectionEnd = endingSelection().end(); | |
| 217 VisiblePosition endAfterSelection = endOfParagraph(endOfLastParagraph.next()
); | |
| 218 | |
| 219 while (endOfCurrentParagraph.deepEquivalent() != endAfterSelection.deepEquiv
alent()) { | |
| 220 VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagrap
h.next()); | |
| 221 if (endOfCurrentParagraph.deepEquivalent() == endOfLastParagraph.deepEqu
ivalent()) | |
| 222 setEndingSelection(VisibleSelection(originalSelectionEnd, DOWNSTREAM
)); | |
| 223 else | |
| 224 setEndingSelection(endOfCurrentParagraph); | |
| 225 | |
| 226 outdentParagraph(); | |
| 227 | |
| 228 // outdentParagraph could move more than one paragraph if the paragraph | |
| 229 // is in a list item. As a result, endAfterSelection and endOfNextParagr
aph | |
| 230 // could refer to positions no longer in the document. | |
| 231 if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent()
.inDocument()) | |
| 232 break; | |
| 233 | |
| 234 if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent
().inDocument()) { | |
| 235 endOfCurrentParagraph = VisiblePosition(endingSelection().end()); | |
| 236 endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); | |
| 237 } | |
| 238 endOfCurrentParagraph = endOfNextParagraph; | |
| 239 } | |
| 240 } | |
| 241 | |
| 242 void IndentOutdentCommand::formatSelection(const VisiblePosition& startOfSelecti
on, const VisiblePosition& endOfSelection) | |
| 243 { | |
| 244 if (m_typeOfAction == Indent) | |
| 245 ApplyBlockElementCommand::formatSelection(startOfSelection, endOfSelecti
on); | |
| 246 else | |
| 247 outdentRegion(startOfSelection, endOfSelection); | |
| 248 } | |
| 249 | |
| 250 void IndentOutdentCommand::formatRange(const Position& start, const Position& en
d, const Position&, RefPtrWillBeRawPtr<HTMLElement>& blockquoteForNextIndent) | |
| 251 { | |
| 252 if (tryIndentingAsListItem(start, end)) | |
| 253 blockquoteForNextIndent = nullptr; | |
| 254 else | |
| 255 indentIntoBlockquote(start, end, blockquoteForNextIndent); | |
| 256 } | |
| 257 | |
| 258 } | |
| OLD | NEW |