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 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, 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 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