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