Add an editing command for creating and inserting child lists
[WebKit-https.git] / Source / WebCore / editing / ModifySelectionListLevel.cpp
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 }