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
37namespace WebCore {
38
39ModifySelectionListLevelCommand::ModifySelectionListLevelCommand(Document& document)
40 : CompositeEditCommand(document)
41{
42}
43
44bool ModifySelectionListLevelCommand::preservesTypingStyle() const
45{
46 return true;
47}
48
49// This needs to be static so it can be called by canIncreaseSelectionListLevel and canDecreaseSelectionListLevel
50static 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
92void 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
107void 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
123void 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
138IncreaseSelectionListLevelCommand::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
145static 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
172void 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
206bool IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(Document* document)
207{
208 Node* startListChild;
209 Node* endListChild;
210 return canIncreaseListLevel(document->frame()->selection().selection(), startListChild, endListChild);
211}
212
213RefPtr<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
222RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document)
223{
224 return increaseSelectionListLevel(document, Type::InheritedListType);
225}
226
227RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(Document* document)
228{
229 return increaseSelectionListLevel(document, Type::OrderedList);
230}
231
232RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(Document* document)
233{
234 return increaseSelectionListLevel(document, Type::UnorderedList);
235}
236
237DecreaseSelectionListLevelCommand::DecreaseSelectionListLevelCommand(Document& document)
238 : ModifySelectionListLevelCommand(document)
239{
240}
241
242// This needs to be static so it can be called by canDecreaseSelectionListLevel
243static 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
255void 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
282bool DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(Document* document)
283{
284 Node* startListChild;
285 Node* endListChild;
286 return canDecreaseListLevel(document->frame()->selection().selection(), startListChild, endListChild);
287}
288
289void DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(Document* document)
290{
291 ASSERT(document);
292 ASSERT(document->frame());
293 create(*document)->apply();
294}
295
296}
297