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 "core/editing/ModifySelectionListLevel.h" 28 29#include "core/dom/Document.h" 30#include "core/editing/FrameSelection.h" 31#include "core/editing/htmlediting.h" 32#include "core/html/HTMLElement.h" 33#include "core/frame/Frame.h" 34#include "core/rendering/RenderObject.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.frame()); 215 RefPtr<IncreaseSelectionListLevelCommand> command = create(document, type); 216 command->apply(); 217 return command->m_listElement.release(); 218} 219 220PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document& document) 221{ 222 return increaseSelectionListLevel(document, InheritedListType); 223} 224 225PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(Document& document) 226{ 227 return increaseSelectionListLevel(document, OrderedList); 228} 229 230PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(Document& document) 231{ 232 return increaseSelectionListLevel(document, UnorderedList); 233} 234 235DecreaseSelectionListLevelCommand::DecreaseSelectionListLevelCommand(Document& document) 236 : ModifySelectionListLevelCommand(document) 237{ 238} 239 240// This needs to be static so it can be called by canDecreaseSelectionListLevel 241static bool canDecreaseListLevel(const VisibleSelection& selection, Node*& start, Node*& end) 242{ 243 if (!getStartEndListChildren(selection, start, end)) 244 return false; 245 246 // there must be a destination list to move the items to 247 if (!isListElement(start->parentNode()->parentNode())) 248 return false; 249 250 return true; 251} 252 253void DecreaseSelectionListLevelCommand::doApply() 254{ 255 Node* startListChild; 256 Node* endListChild; 257 if (!canDecreaseListLevel(endingSelection(), startListChild, endListChild)) 258 return; 259 260 Node* previousItem = startListChild->renderer()->previousSibling() ? startListChild->renderer()->previousSibling()->node() : 0; 261 Node* nextItem = endListChild->renderer()->nextSibling() ? endListChild->renderer()->nextSibling()->node() : 0; 262 Element* listNode = startListChild->parentElement(); 263 264 if (!previousItem) { 265 // at start of sublist, move the child(ren) to before the sublist 266 insertSiblingNodeRangeBefore(startListChild, endListChild, listNode); 267 // if that was the whole sublist we moved, remove the sublist node 268 if (!nextItem) 269 removeNode(listNode); 270 } else if (!nextItem) { 271 // at end of list, move the child(ren) to after the sublist 272 insertSiblingNodeRangeAfter(startListChild, endListChild, listNode); 273 } else if (listNode) { 274 // in the middle of list, split the list and move the children to the divide 275 splitElement(listNode, startListChild); 276 insertSiblingNodeRangeBefore(startListChild, endListChild, listNode); 277 } 278} 279 280bool DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(Document& document) 281{ 282 Node* startListChild; 283 Node* endListChild; 284 return canDecreaseListLevel(document.frame()->selection().selection(), startListChild, endListChild); 285} 286 287void DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(Document& document) 288{ 289 ASSERT(document.frame()); 290 create(document)->apply(); 291} 292 293} 294