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 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 "IndentOutdentCommand.h" 28 29 #include "Document.h" 30 #include "Element.h" 31 #include "HTMLBlockquoteElement.h" 32 #include "HTMLNames.h" 33 #include "InsertLineBreakCommand.h" 34 #include "InsertListCommand.h" 35 #include "Range.h" 36 #include "SplitElementCommand.h" 37 #include "Text.h" 38 #include "TextIterator.h" 39 #include "htmlediting.h" 40 #include "visible_units.h" 41 #include <wtf/StdLibExtras.h> 42 43 namespace WebCore { 44 45 using namespace HTMLNames; 46 47 static bool isListOrIndentBlockquote(const Node* node) 48 { 49 return node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(blockquoteTag)); 50 } 51 52 IndentOutdentCommand::IndentOutdentCommand(Document* document, EIndentType typeOfAction, int marginInPixels) 53 : ApplyBlockElementCommand(document, blockquoteTag, "webkit-indent-blockquote", "margin: 0 0 0 40px; border: none; padding: 0px;") 54 , m_typeOfAction(typeOfAction) 55 , m_marginInPixels(marginInPixels) 56 { 57 } 58 59 bool IndentOutdentCommand::tryIndentingAsListItem(const Position& start, const Position& end) 60 { 61 // If our selection is not inside a list, bail out. 62 Node* lastNodeInSelectedParagraph = start.deprecatedNode(); 63 RefPtr<Element> listNode = enclosingList(lastNodeInSelectedParagraph); 64 if (!listNode) 65 return false; 66 67 // 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. 68 Element* selectedListItem = static_cast<Element*>(enclosingBlock(lastNodeInSelectedParagraph)); 69 70 // FIXME: we need to deal with the case where there is no li (malformed HTML) 71 if (!selectedListItem->hasTagName(liTag)) 72 return false; 73 74 // FIXME: previousElementSibling does not ignore non-rendered content like <span></span>. Should we? 75 Element* previousList = selectedListItem->previousElementSibling(); 76 Element* nextList = selectedListItem->nextElementSibling(); 77 78 RefPtr<Element> newList = document()->createElement(listNode->tagQName(), false); 79 insertNodeBefore(newList, selectedListItem); 80 81 moveParagraphWithClones(start, end, newList.get(), selectedListItem); 82 83 if (canMergeLists(previousList, newList.get())) 84 mergeIdenticalElements(previousList, newList); 85 if (canMergeLists(newList.get(), nextList)) 86 mergeIdenticalElements(newList, nextList); 87 88 return true; 89 } 90 91 void IndentOutdentCommand::indentIntoBlockquote(const Position& start, const Position& end, RefPtr<Element>& targetBlockquote) 92 { 93 Node* enclosingCell = enclosingNodeOfType(start, &isTableCell); 94 Node* nodeToSplitTo; 95 if (enclosingCell) 96 nodeToSplitTo = enclosingCell; 97 else if (enclosingList(start.deprecatedNode())) 98 nodeToSplitTo = enclosingBlock(start.deprecatedNode()); 99 else 100 nodeToSplitTo = editableRootForPosition(start); 101 102 if (!nodeToSplitTo) 103 return; 104 105 RefPtr<Node> outerBlock = (start.deprecatedNode() == nodeToSplitTo) ? start.deprecatedNode() : splitTreeToNode(start.deprecatedNode(), nodeToSplitTo); 106 107 if (!targetBlockquote) { 108 // Create a new blockquote and insert it as a child of the root editable element. We accomplish 109 // this by splitting all parents of the current paragraph up to that point. 110 targetBlockquote = createBlockElement(); 111 insertNodeBefore(targetBlockquote, outerBlock); 112 } 113 114 moveParagraphWithClones(start, end, targetBlockquote.get(), outerBlock.get()); 115 } 116 117 void IndentOutdentCommand::outdentParagraph() 118 { 119 VisiblePosition visibleStartOfParagraph = startOfParagraph(endingSelection().visibleStart()); 120 VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph); 121 122 Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote); 123 if (!enclosingNode || !enclosingNode->parentNode()->rendererIsEditable()) // We can't outdent if there is no place to go! 124 return; 125 126 // Use InsertListCommand to remove the selection from the list 127 if (enclosingNode->hasTagName(olTag)) { 128 applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::OrderedList)); 129 return; 130 } 131 if (enclosingNode->hasTagName(ulTag)) { 132 applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::UnorderedList)); 133 return; 134 } 135 136 // The selection is inside a blockquote i.e. enclosingNode is a blockquote 137 VisiblePosition positionInEnclosingBlock = VisiblePosition(firstPositionInNode(enclosingNode)); 138 // If the blockquote is inline, the start of the enclosing block coincides with 139 // positionInEnclosingBlock. 140 VisiblePosition startOfEnclosingBlock = (enclosingNode->renderer() && enclosingNode->renderer()->isInline()) ? positionInEnclosingBlock : startOfBlock(positionInEnclosingBlock); 141 VisiblePosition lastPositionInEnclosingBlock = VisiblePosition(lastPositionInNode(enclosingNode)); 142 VisiblePosition endOfEnclosingBlock = endOfBlock(lastPositionInEnclosingBlock); 143 if (visibleStartOfParagraph == startOfEnclosingBlock && 144 visibleEndOfParagraph == endOfEnclosingBlock) { 145 // The blockquote doesn't contain anything outside the paragraph, so it can be totally removed. 146 Node* splitPoint = enclosingNode->nextSibling(); 147 removeNodePreservingChildren(enclosingNode); 148 // outdentRegion() assumes it is operating on the first paragraph of an enclosing blockquote, but if there are multiply nested blockquotes and we've 149 // just removed one, then this assumption isn't true. By splitting the next containing blockquote after this node, we keep this assumption true 150 if (splitPoint) { 151 if (ContainerNode* splitPointParent = splitPoint->parentNode()) { 152 if (splitPointParent->hasTagName(blockquoteTag) 153 && !splitPoint->hasTagName(blockquoteTag) 154 && splitPointParent->parentNode()->rendererIsEditable()) // We can't outdent if there is no place to go! 155 splitElement(static_cast<Element*>(splitPointParent), splitPoint); 156 } 157 } 158 159 updateLayout(); 160 visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEquivalent()); 161 visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquivalent()); 162 if (visibleStartOfParagraph.isNotNull() && !isStartOfParagraph(visibleStartOfParagraph)) 163 insertNodeAt(createBreakElement(document()), visibleStartOfParagraph.deepEquivalent()); 164 if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfParagraph)) 165 insertNodeAt(createBreakElement(document()), visibleEndOfParagraph.deepEquivalent()); 166 167 return; 168 } 169 Node* enclosingBlockFlow = enclosingBlock(visibleStartOfParagraph.deepEquivalent().deprecatedNode()); 170 RefPtr<Node> splitBlockquoteNode = enclosingNode; 171 if (enclosingBlockFlow != enclosingNode) 172 splitBlockquoteNode = splitTreeToNode(enclosingBlockFlow, enclosingNode, true); 173 else { 174 // We split the blockquote at where we start outdenting. 175 splitElement(static_cast<Element*>(enclosingNode), visibleStartOfParagraph.deepEquivalent().deprecatedNode()); 176 } 177 RefPtr<Node> placeholder = createBreakElement(document()); 178 insertNodeBefore(placeholder, splitBlockquoteNode); 179 moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), positionBeforeNode(placeholder.get()), true); 180 } 181 182 // FIXME: We should merge this function with ApplyBlockElementCommand::formatSelection 183 void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) 184 { 185 VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection); 186 187 if (endOfParagraph(startOfSelection) == endOfLastParagraph) { 188 outdentParagraph(); 189 return; 190 } 191 192 Position originalSelectionEnd = endingSelection().end(); 193 VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); 194 VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next()); 195 196 while (endOfCurrentParagraph != endAfterSelection) { 197 VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); 198 if (endOfCurrentParagraph == endOfLastParagraph) 199 setEndingSelection(VisibleSelection(originalSelectionEnd, DOWNSTREAM)); 200 else 201 setEndingSelection(endOfCurrentParagraph); 202 203 outdentParagraph(); 204 205 // outdentParagraph could move more than one paragraph if the paragraph 206 // is in a list item. As a result, endAfterSelection and endOfNextParagraph 207 // could refer to positions no longer in the document. 208 if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().anchorNode()->inDocument()) 209 break; 210 211 if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().anchorNode()->inDocument()) { 212 endOfCurrentParagraph = endingSelection().end(); 213 endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); 214 } 215 endOfCurrentParagraph = endOfNextParagraph; 216 } 217 } 218 219 void IndentOutdentCommand::formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) 220 { 221 if (m_typeOfAction == Indent) 222 ApplyBlockElementCommand::formatSelection(startOfSelection, endOfSelection); 223 else 224 outdentRegion(startOfSelection, endOfSelection); 225 } 226 227 void IndentOutdentCommand::formatRange(const Position& start, const Position& end, const Position&, RefPtr<Element>& blockquoteForNextIndent) 228 { 229 if (tryIndentingAsListItem(start, end)) 230 blockquoteForNextIndent = 0; 231 else 232 indentIntoBlockquote(start, end, blockquoteForNextIndent); 233 } 234 235 } 236