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