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 |