Home | History | Annotate | Download | only in editing
      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 "HTMLElement.h"
     32 #include "RenderObject.h"
     33 #include "SelectionController.h"
     34 #include "htmlediting.h"
     35 
     36 namespace WebCore {
     37 
     38 ModifySelectionListLevelCommand::ModifySelectionListLevelCommand(Document* document)
     39     : CompositeEditCommand(document)
     40 {
     41 }
     42 
     43 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
     49 static 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 
     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 
    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 
    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 
    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
    144 static 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
    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()->node();
    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->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 
    205 bool IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(Document* document)
    206 {
    207     Node* startListChild;
    208     Node* endListChild;
    209     return canIncreaseListLevel(document->frame()->selection()->selection(), startListChild, endListChild);
    210 }
    211 
    212 PassRefPtr<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 
    221 PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document)
    222 {
    223     return increaseSelectionListLevel(document, InheritedListType);
    224 }
    225 
    226 PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(Document* document)
    227 {
    228     return increaseSelectionListLevel(document, OrderedList);
    229 }
    230 
    231 PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(Document* document)
    232 {
    233     return increaseSelectionListLevel(document, UnorderedList);
    234 }
    235 
    236 DecreaseSelectionListLevelCommand::DecreaseSelectionListLevelCommand(Document* document)
    237     : ModifySelectionListLevelCommand(document)
    238 {
    239 }
    240 
    241 // This needs to be static so it can be called by canDecreaseSelectionListLevel
    242 static 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 
    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()->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 
    281 bool DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(Document* document)
    282 {
    283     Node* startListChild;
    284     Node* endListChild;
    285     return canDecreaseListLevel(document->frame()->selection()->selection(), startListChild, endListChild);
    286 }
    287 
    288 void DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(Document* document)
    289 {
    290     ASSERT(document);
    291     ASSERT(document->frame());
    292     applyCommand(create(document));
    293 }
    294 
    295 }
    296