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 "Frame.h"
31#include "FrameSelection.h"
32#include "HTMLElement.h"
33#include "RenderObject.h"
34#include "htmlediting.h"
35
36namespace WebCore {
37
38ModifySelectionListLevelCommand::ModifySelectionListLevelCommand(Document* document)
39    : CompositeEditCommand(document)
40{
41}
42
43bool ModifySelectionListLevelCommand::preservesTypingStyle() const
44{
45    return true;
46}
47
48// This needs to be static so it can be called by canIncreaseSelectionListLevel and canDecreaseSelectionListLevel
49static bool getStartEndListChildren(const VisibleSelection& 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().anchorNode());
56    if (!startListChild)
57        return false;
58
59    // end must be in a list child
60    Node* endListChild = selection.isRange() ? enclosingListChild(selection.end().anchorNode()) : 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->node()))
83            endListChild = r->node();
84    }
85
86    start = startListChild;
87    end = endListChild;
88    return true;
89}
90
91void 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
106void 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
122void 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
137IncreaseSelectionListLevelCommand::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
144static bool canIncreaseListLevel(const VisibleSelection& 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
171void IncreaseSelectionListLevelCommand::doApply()
172{
173    Node* startListChild;
174    Node* endListChild;
175    if (!canIncreaseListLevel(endingSelection(), startListChild, endListChild))
176        return;
177
178    Node* previousItem = startListChild->renderer()->previousSibling()->node();
179    if (isListElement(previousItem)) {
180        // move nodes up into preceding list
181        appendSiblingNodeRange(startListChild, endListChild, toElement(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->cloneElementWithoutChildren();
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
205bool IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(Document* document)
206{
207    Node* startListChild;
208    Node* endListChild;
209    return canIncreaseListLevel(document->frame()->selection()->selection(), startListChild, endListChild);
210}
211
212PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document, Type type)
213{
214    ASSERT(document);
215    ASSERT(document->frame());
216    RefPtr<IncreaseSelectionListLevelCommand> command = create(document, type);
217    command->apply();
218    return command->m_listElement.release();
219}
220
221PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document)
222{
223    return increaseSelectionListLevel(document, InheritedListType);
224}
225
226PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(Document* document)
227{
228    return increaseSelectionListLevel(document, OrderedList);
229}
230
231PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(Document* document)
232{
233    return increaseSelectionListLevel(document, UnorderedList);
234}
235
236DecreaseSelectionListLevelCommand::DecreaseSelectionListLevelCommand(Document* document)
237    : ModifySelectionListLevelCommand(document)
238{
239}
240
241// This needs to be static so it can be called by canDecreaseSelectionListLevel
242static bool canDecreaseListLevel(const VisibleSelection& 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
254void 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()->node() : 0;
262    Node* nextItem = endListChild->renderer()->nextSibling() ? endListChild->renderer()->nextSibling()->node() : 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
281bool DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(Document* document)
282{
283    Node* startListChild;
284    Node* endListChild;
285    return canDecreaseListLevel(document->frame()->selection()->selection(), startListChild, endListChild);
286}
287
288void DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(Document* document)
289{
290    ASSERT(document);
291    ASSERT(document->frame());
292    applyCommand(create(document));
293}
294
295}
296