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 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 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 "ModifySelectionListLevel.h" |
28 | |
29 | #include "Document.h" |
30 | #include "Editing.h" |
31 | #include "Frame.h" |
32 | #include "FrameSelection.h" |
33 | #include "HTMLOListElement.h" |
34 | #include "HTMLUListElement.h" |
35 | #include "RenderObject.h" |
36 | |
37 | namespace WebCore { |
38 | |
39 | ModifySelectionListLevelCommand::ModifySelectionListLevelCommand(Document& document) |
40 | : CompositeEditCommand(document) |
41 | { |
42 | } |
43 | |
44 | bool ModifySelectionListLevelCommand::preservesTypingStyle() const |
45 | { |
46 | return true; |
47 | } |
48 | |
49 | // This needs to be static so it can be called by canIncreaseSelectionListLevel and canDecreaseSelectionListLevel |
50 | static bool getStartEndListChildren(const VisibleSelection& selection, Node*& start, Node*& end) |
51 | { |
52 | if (selection.isNone()) |
53 | return false; |
54 | |
55 | // start must be in a list child |
56 | Node* startListChild = enclosingListChild(selection.start().anchorNode()); |
57 | if (!startListChild) |
58 | return false; |
59 | |
60 | // end must be in a list child |
61 | Node* endListChild = selection.isRange() ? enclosingListChild(selection.end().anchorNode()) : startListChild; |
62 | if (!endListChild) |
63 | return false; |
64 | |
65 | // For a range selection we want the following behavior: |
66 | // - the start and end must be within the same overall list |
67 | // - the start must be at or above the level of the rest of the range |
68 | // - if the end is anywhere in a sublist lower than start, the whole sublist gets moved |
69 | // In terms of this function, this means: |
70 | // - endListChild must start out being be a sibling of startListChild, or be in a |
71 | // sublist of startListChild or a sibling |
72 | // - if endListChild is in a sublist of startListChild or a sibling, it must be adjusted |
73 | // to be the ancestor that is startListChild or its sibling |
74 | while (startListChild->parentNode() != endListChild->parentNode()) { |
75 | endListChild = endListChild->parentNode(); |
76 | if (!endListChild) |
77 | return false; |
78 | } |
79 | |
80 | // if the selection ends on a list item with a sublist, include the entire sublist |
81 | if (endListChild->renderer()->isListItem()) { |
82 | RenderObject* r = endListChild->renderer()->nextSibling(); |
83 | if (r && isListHTMLElement(r->node())) |
84 | endListChild = r->node(); |
85 | } |
86 | |
87 | start = startListChild; |
88 | end = endListChild; |
89 | return true; |
90 | } |
91 | |
92 | void ModifySelectionListLevelCommand::insertSiblingNodeRangeBefore(Node* startNode, Node* endNode, Node* refNode) |
93 | { |
94 | Node* node = startNode; |
95 | while (1) { |
96 | Node* next = node->nextSibling(); |
97 | removeNode(*node); |
98 | insertNodeBefore(*node, *refNode); |
99 | |
100 | if (node == endNode) |
101 | break; |
102 | |
103 | node = next; |
104 | } |
105 | } |
106 | |
107 | void ModifySelectionListLevelCommand::insertSiblingNodeRangeAfter(Node* startNode, Node* endNode, Node* refNode) |
108 | { |
109 | Node* node = startNode; |
110 | while (1) { |
111 | Node* next = node->nextSibling(); |
112 | removeNode(*node); |
113 | insertNodeAfter(*node, *refNode); |
114 | |
115 | if (node == endNode) |
116 | break; |
117 | |
118 | refNode = node; |
119 | node = next; |
120 | } |
121 | } |
122 | |
123 | void ModifySelectionListLevelCommand::appendSiblingNodeRange(Node* startNode, Node* endNode, Element* newParent) |
124 | { |
125 | Node* node = startNode; |
126 | while (1) { |
127 | Node* next = node->nextSibling(); |
128 | removeNode(*node); |
129 | appendNode(*node, *newParent); |
130 | |
131 | if (node == endNode) |
132 | break; |
133 | |
134 | node = next; |
135 | } |
136 | } |
137 | |
138 | IncreaseSelectionListLevelCommand::IncreaseSelectionListLevelCommand(Document& document, Type listType) |
139 | : ModifySelectionListLevelCommand(document) |
140 | , m_listType(listType) |
141 | { |
142 | } |
143 | |
144 | // This needs to be static so it can be called by canIncreaseSelectionListLevel |
145 | static bool canIncreaseListLevel(const VisibleSelection& selection, Node*& start, Node*& end) |
146 | { |
147 | if (!getStartEndListChildren(selection, start, end)) |
148 | return false; |
149 | |
150 | // start must not be the first child (because you need a prior one |
151 | // to increase relative to) |
152 | if (!start->renderer()->previousSibling()) |
153 | return false; |
154 | |
155 | return true; |
156 | } |
157 | |
158 | // For the moment, this is SPI and the only client (Mail.app) is satisfied. |
159 | // Here are two things to re-evaluate when making into API. |
160 | // 1. Currently, InheritedListType uses clones whereas OrderedList and |
161 | // UnorderedList create a new list node of the specified type. That is |
162 | // inconsistent wrt style. If that is not OK, here are some alternatives: |
163 | // - new nodes always inherit style (probably the best choice) |
164 | // - new nodes have always have no style |
165 | // - new nodes of the same type inherit style |
166 | // 2. Currently, the node we return may be either a pre-existing one or |
167 | // a new one. Is it confusing to return the pre-existing one without |
168 | // somehow indicating that it is not new? If so, here are some alternatives: |
169 | // - only return the list node if we created it |
170 | // - indicate whether the list node is new or pre-existing |
171 | // - (silly) client specifies whether to return pre-existing list nodes |
172 | void IncreaseSelectionListLevelCommand::doApply() |
173 | { |
174 | Node* startListChild; |
175 | Node* endListChild; |
176 | if (!canIncreaseListLevel(endingSelection(), startListChild, endListChild)) |
177 | return; |
178 | |
179 | Node* previousItem = startListChild->renderer()->previousSibling()->node(); |
180 | if (isListHTMLElement(previousItem)) { |
181 | // move nodes up into preceding list |
182 | appendSiblingNodeRange(startListChild, endListChild, downcast<Element>(previousItem)); |
183 | m_listElement = previousItem; |
184 | } else { |
185 | // create a sublist for the preceding element and move nodes there |
186 | RefPtr<Element> newParent; |
187 | switch (m_listType) { |
188 | case Type::InheritedListType: |
189 | newParent = startListChild->parentElement(); |
190 | if (newParent) |
191 | newParent = newParent->cloneElementWithoutChildren(document()); |
192 | break; |
193 | case Type::OrderedList: |
194 | newParent = HTMLOListElement::create(document()); |
195 | break; |
196 | case Type::UnorderedList: |
197 | newParent = HTMLUListElement::create(document()); |
198 | break; |
199 | } |
200 | insertNodeBefore(*newParent, *startListChild); |
201 | appendSiblingNodeRange(startListChild, endListChild, newParent.get()); |
202 | m_listElement = WTFMove(newParent); |
203 | } |
204 | } |
205 | |
206 | bool IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(Document* document) |
207 | { |
208 | Node* startListChild; |
209 | Node* endListChild; |
210 | return canIncreaseListLevel(document->frame()->selection().selection(), startListChild, endListChild); |
211 | } |
212 | |
213 | RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document, Type type) |
214 | { |
215 | ASSERT(document); |
216 | ASSERT(document->frame()); |
217 | auto command = create(*document, type); |
218 | command->apply(); |
219 | return WTFMove(command->m_listElement); |
220 | } |
221 | |
222 | RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document) |
223 | { |
224 | return increaseSelectionListLevel(document, Type::InheritedListType); |
225 | } |
226 | |
227 | RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(Document* document) |
228 | { |
229 | return increaseSelectionListLevel(document, Type::OrderedList); |
230 | } |
231 | |
232 | RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(Document* document) |
233 | { |
234 | return increaseSelectionListLevel(document, Type::UnorderedList); |
235 | } |
236 | |
237 | DecreaseSelectionListLevelCommand::DecreaseSelectionListLevelCommand(Document& document) |
238 | : ModifySelectionListLevelCommand(document) |
239 | { |
240 | } |
241 | |
242 | // This needs to be static so it can be called by canDecreaseSelectionListLevel |
243 | static bool canDecreaseListLevel(const VisibleSelection& selection, Node*& start, Node*& end) |
244 | { |
245 | if (!getStartEndListChildren(selection, start, end)) |
246 | return false; |
247 | |
248 | // there must be a destination list to move the items to |
249 | if (!isListHTMLElement(start->parentNode()->parentNode())) |
250 | return false; |
251 | |
252 | return true; |
253 | } |
254 | |
255 | void DecreaseSelectionListLevelCommand::doApply() |
256 | { |
257 | Node* startListChild; |
258 | Node* endListChild; |
259 | if (!canDecreaseListLevel(endingSelection(), startListChild, endListChild)) |
260 | return; |
261 | |
262 | Node* previousItem = startListChild->renderer()->previousSibling() ? startListChild->renderer()->previousSibling()->node() : 0; |
263 | Node* nextItem = endListChild->renderer()->nextSibling() ? endListChild->renderer()->nextSibling()->node() : 0; |
264 | Element* listNode = startListChild->parentElement(); |
265 | |
266 | if (!previousItem) { |
267 | // at start of sublist, move the child(ren) to before the sublist |
268 | insertSiblingNodeRangeBefore(startListChild, endListChild, listNode); |
269 | // if that was the whole sublist we moved, remove the sublist node |
270 | if (!nextItem && listNode) |
271 | removeNode(*listNode); |
272 | } else if (!nextItem) { |
273 | // at end of list, move the child(ren) to after the sublist |
274 | insertSiblingNodeRangeAfter(startListChild, endListChild, listNode); |
275 | } else if (listNode) { |
276 | // in the middle of list, split the list and move the children to the divide |
277 | splitElement(*listNode, *startListChild); |
278 | insertSiblingNodeRangeBefore(startListChild, endListChild, listNode); |
279 | } |
280 | } |
281 | |
282 | bool DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(Document* document) |
283 | { |
284 | Node* startListChild; |
285 | Node* endListChild; |
286 | return canDecreaseListLevel(document->frame()->selection().selection(), startListChild, endListChild); |
287 | } |
288 | |
289 | void DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(Document* document) |
290 | { |
291 | ASSERT(document); |
292 | ASSERT(document->frame()); |
293 | create(*document)->apply(); |
294 | } |
295 | |
296 | } |
297 | |