ModifySelectionListLevel.cpp 10.6 KB
Newer Older
harrison's avatar
harrison committed
1
/*
darin@apple.com's avatar
darin@apple.com committed
2
 * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
harrison's avatar
harrison committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "config.h"
harrison's avatar
harrison committed
27
#include "ModifySelectionListLevel.h"
harrison's avatar
harrison committed
28

darin's avatar
darin committed
29
#include "Document.h"
30
#include "Element.h"
harrison's avatar
harrison committed
31
#include "Frame.h"
darin's avatar
darin committed
32
#include "RenderObject.h"
harrison's avatar
harrison committed
33 34 35 36 37
#include "SelectionController.h"
#include "htmlediting.h"

namespace WebCore {

harrison's avatar
harrison committed
38
ModifySelectionListLevelCommand::ModifySelectionListLevelCommand(Document* document) 
harrison's avatar
harrison committed
39 40 41 42 43 44 45 46 47
    : CompositeEditCommand(document)
{
}

bool ModifySelectionListLevelCommand::preservesTypingStyle() const
{
    return true;
}

harrison's avatar
harrison committed
48
// This needs to be static so it can be called by canIncreaseSelectionListLevel and canDecreaseSelectionListLevel
darin's avatar
darin committed
49
static bool getStartEndListChildren(const Selection& selection, Node*& start, Node*& end)
harrison's avatar
harrison committed
50 51 52 53 54
{
    if (selection.isNone())
        return false;

    // start must be in a list child
darin's avatar
darin committed
55
    Node* startListChild = enclosingListChild(selection.start().node());
harrison's avatar
harrison committed
56 57 58 59
    if (!startListChild)
        return false;
        
    // end must be in a list child
darin's avatar
darin committed
60
    Node* endListChild = selection.isRange() ? enclosingListChild(selection.end().node()) : startListChild;
harrison's avatar
harrison committed
61 62 63 64 65 66
    if (!endListChild)
        return false;
    
    // For a range selection we want the following behavior:
    //      - the start and end must be within the same overall list
    //      - the start must be at or above the level of the rest of the range
harrison's avatar
harrison committed
67
    //      - if the end is anywhere in a sublist lower than start, the whole sublist gets moved
harrison's avatar
harrison committed
68 69 70 71 72 73 74 75 76 77 78
    // In terms of this function, this means:
    //      - endListChild must start out being be a sibling of startListChild, or be in a
    //         sublist of startListChild or a sibling
    //      - if endListChild is in a sublist of startListChild or a sibling, it must be adjusted
    //         to be the ancestor that is startListChild or its sibling
    while (startListChild->parentNode() != endListChild->parentNode()) {
        endListChild = endListChild->parentNode();
        if (!endListChild)
            return false;
    }
    
harrison's avatar
harrison committed
79
    // if the selection ends on a list item with a sublist, include the entire sublist
harrison's avatar
harrison committed
80 81 82 83 84 85
    if (endListChild->renderer()->isListItem()) {
        RenderObject* r = endListChild->renderer()->nextSibling();
        if (r && isListElement(r->element()))
            endListChild = r->element();
    }

darin's avatar
darin committed
86 87
    start = startListChild;
    end = endListChild;
harrison's avatar
harrison committed
88 89 90
    return true;
}

darin's avatar
darin committed
91
void ModifySelectionListLevelCommand::insertSiblingNodeRangeBefore(Node* startNode, Node* endNode, Node* refNode)
harrison's avatar
harrison committed
92
{
darin's avatar
darin committed
93
    Node* node = startNode;
harrison's avatar
harrison committed
94
    while (1) {
darin's avatar
darin committed
95
        Node* next = node->nextSibling();
harrison's avatar
harrison committed
96 97 98 99 100
        removeNode(node);
        insertNodeBefore(node, refNode);

        if (node == endNode)
            break;
harrison's avatar
harrison committed
101

harrison's avatar
harrison committed
102 103 104 105
        node = next;
    }
}

darin's avatar
darin committed
106
void ModifySelectionListLevelCommand::insertSiblingNodeRangeAfter(Node* startNode, Node* endNode, Node* refNode)
harrison's avatar
harrison committed
107
{
darin's avatar
darin committed
108
    Node* node = startNode;
harrison's avatar
harrison committed
109
    while (1) {
darin's avatar
darin committed
110
        Node* next = node->nextSibling();
harrison's avatar
harrison committed
111 112 113 114 115 116 117 118 119 120 121
        removeNode(node);
        insertNodeAfter(node, refNode);

        if (node == endNode)
            break;

        refNode = node;
        node = next;
    }
}

darin's avatar
darin committed
122
void ModifySelectionListLevelCommand::appendSiblingNodeRange(Node* startNode, Node* endNode, Node* newParent)
harrison's avatar
harrison committed
123
{
darin's avatar
darin committed
124
    Node* node = startNode;
harrison's avatar
harrison committed
125
    while (1) {
darin's avatar
darin committed
126
        Node* next = node->nextSibling();
harrison's avatar
harrison committed
127 128 129 130 131
        removeNode(node);
        appendNode(node, newParent);

        if (node == endNode)
            break;
harrison's avatar
harrison committed
132

harrison's avatar
harrison committed
133 134 135 136
        node = next;
    }
}

darin's avatar
darin committed
137
IncreaseSelectionListLevelCommand::IncreaseSelectionListLevelCommand(Document* document, Type listType)
harrison's avatar
harrison committed
138
    : ModifySelectionListLevelCommand(document)
darin's avatar
darin committed
139
    , m_listType(listType)
harrison's avatar
harrison committed
140 141 142 143
{
}

// This needs to be static so it can be called by canIncreaseSelectionListLevel
darin's avatar
darin committed
144
static bool canIncreaseListLevel(const Selection& selection, Node*& start, Node*& end)
harrison's avatar
harrison committed
145 146 147 148 149 150
{
    if (!getStartEndListChildren(selection, start, end))
        return false;
        
    // start must not be the first child (because you need a prior one
    // to increase relative to)
darin's avatar
darin committed
151
    if (!start->renderer()->previousSibling())
harrison's avatar
harrison committed
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
        return false;
    
    return true;
}

// For the moment, this is SPI and the only client (Mail.app) is satisfied.
// Here are two things to re-evaluate when making into API.
// 1. Currently, InheritedListType uses clones whereas OrderedList and
// UnorderedList create a new list node of the specified type.  That is
// inconsistent wrt style.  If that is not OK, here are some alternatives:
//  - new nodes always inherit style (probably the best choice)
//  - new nodes have always have no style
//  - new nodes of the same type inherit style
// 2. Currently, the node we return may be either a pre-existing one or
// a new one. Is it confusing to return the pre-existing one without
// somehow indicating that it is not new?  If so, here are some alternatives:
//  - only return the list node if we created it
//  - indicate whether the list node is new or pre-existing
//  - (silly) client specifies whether to return pre-existing list nodes
void IncreaseSelectionListLevelCommand::doApply()
harrison's avatar
harrison committed
172
{
darin's avatar
darin committed
173 174
    Node* startListChild;
    Node* endListChild;
darin's avatar
darin committed
175
    if (!canIncreaseListLevel(endingSelection(), startListChild, endListChild))
harrison's avatar
harrison committed
176 177
        return;

darin's avatar
darin committed
178
    Node* previousItem = startListChild->renderer()->previousSibling()->element();
harrison's avatar
harrison committed
179 180 181
    if (isListElement(previousItem)) {
        // move nodes up into preceding list
        appendSiblingNodeRange(startListChild, endListChild, previousItem);
harrison's avatar
harrison committed
182
        m_listElement = previousItem;
harrison's avatar
harrison committed
183 184
    } else {
        // create a sublist for the preceding element and move nodes there
harrison's avatar
harrison committed
185 186 187 188 189 190 191 192 193 194 195 196
        RefPtr<Node> newParent;
        switch (m_listType) {
            case InheritedListType:
                newParent = startListChild->parentNode()->cloneNode(false);
                break;
            case OrderedList:
                newParent = createOrderedListElement(document());
                break;
            case UnorderedList:
                newParent = createUnorderedListElement(document());
                break;
        }
harrison's avatar
harrison committed
197 198
        insertNodeBefore(newParent.get(), startListChild);
        appendSiblingNodeRange(startListChild, endListChild, newParent.get());
harrison's avatar
harrison committed
199
        m_listElement = newParent.get();
harrison's avatar
harrison committed
200 201 202
    }
}

harrison's avatar
harrison committed
203 204
bool IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(Document* document)
{
darin's avatar
darin committed
205 206
    Node* startListChild;
    Node* endListChild;
darin@apple.com's avatar
darin@apple.com committed
207
    return canIncreaseListLevel(document->frame()->selection()->selection(), startListChild, endListChild);
harrison's avatar
harrison committed
208 209
}

harrison's avatar
harrison committed
210
PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelWithType(Document* document, Type listType)
harrison's avatar
harrison committed
211 212 213
{
    ASSERT(document);
    ASSERT(document->frame());
darin's avatar
darin committed
214 215
    RefPtr<IncreaseSelectionListLevelCommand> modCommand = new IncreaseSelectionListLevelCommand(document, listType);
    modCommand->apply();
darin@apple.com's avatar
darin@apple.com committed
216
    return modCommand->m_listElement;
harrison's avatar
harrison committed
217 218
}

harrison's avatar
harrison committed
219
PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document)
darin's avatar
darin committed
220
{
harrison's avatar
harrison committed
221 222 223
    return increaseSelectionListLevelWithType(document, InheritedListType);
}

harrison's avatar
harrison committed
224
PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(Document* document)
darin's avatar
darin committed
225
{
harrison's avatar
harrison committed
226 227 228
    return increaseSelectionListLevelWithType(document, OrderedList);
}

harrison's avatar
harrison committed
229
PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(Document* document)
darin's avatar
darin committed
230
{
harrison's avatar
harrison committed
231 232 233 234 235 236 237 238 239
    return increaseSelectionListLevelWithType(document, UnorderedList);
}

DecreaseSelectionListLevelCommand::DecreaseSelectionListLevelCommand(Document* document) 
    : ModifySelectionListLevelCommand(document)
{
}

// This needs to be static so it can be called by canDecreaseSelectionListLevel
darin's avatar
darin committed
240
static bool canDecreaseListLevel(const Selection& selection, Node*& start, Node*& end)
harrison's avatar
harrison committed
241 242 243 244 245
{
    if (!getStartEndListChildren(selection, start, end))
        return false;
    
    // there must be a destination list to move the items to
darin's avatar
darin committed
246
    if (!isListElement(start->parentNode()->parentNode()))
harrison's avatar
harrison committed
247 248 249 250 251 252 253 254 255
        return false;
        
    return true;
}

void DecreaseSelectionListLevelCommand::doApply()
{
    Node* startListChild;
    Node* endListChild;
darin's avatar
darin committed
256
    if (!canDecreaseListLevel(endingSelection(), startListChild, endListChild))
harrison's avatar
harrison committed
257 258
        return;

darin's avatar
darin committed
259 260 261
    Node* previousItem = startListChild->renderer()->previousSibling() ? startListChild->renderer()->previousSibling()->element() : 0;
    Node* nextItem = endListChild->renderer()->nextSibling() ? endListChild->renderer()->nextSibling()->element() : 0;
    Node* listNode = startListChild->parentNode();
harrison's avatar
harrison committed
262 263 264 265 266 267 268 269 270 271 272 273

    if (!previousItem) {
        // at start of sublist, move the child(ren) to before the sublist
        insertSiblingNodeRangeBefore(startListChild, endListChild, listNode);
        // if that was the whole sublist we moved, remove the sublist node
        if (!nextItem)
            removeNode(listNode);
    } else if (!nextItem) {
        // at end of list, move the child(ren) to after the sublist
        insertSiblingNodeRangeAfter(startListChild, endListChild, listNode);    
    } else {
        // in the middle of list, split the list and move the children to the divide
darin's avatar
darin committed
274
        splitElement(static_cast<Element*>(listNode), startListChild);
harrison's avatar
harrison committed
275 276
        insertSiblingNodeRangeBefore(startListChild, endListChild, listNode);
    }
harrison's avatar
harrison committed
277 278 279 280 281 282
}

bool DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(Document* document)
{
    Node* startListChild;
    Node* endListChild;
darin@apple.com's avatar
darin@apple.com committed
283
    return canDecreaseListLevel(document->frame()->selection()->selection(), startListChild, endListChild);
harrison's avatar
harrison committed
284 285 286 287 288 289
}

void DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(Document* document)
{
    ASSERT(document);
    ASSERT(document->frame());
darin's avatar
darin committed
290
    applyCommand(new DecreaseSelectionListLevelCommand(document));
harrison's avatar
harrison committed
291 292 293
}

}