| /* |
| * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. |
| * |
| * 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 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 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" |
| #include "ModifySelectionListLevel.h" |
| |
| #include "Document.h" |
| #include "Editing.h" |
| #include "ElementInlines.h" |
| #include "Frame.h" |
| #include "FrameSelection.h" |
| #include "HTMLOListElement.h" |
| #include "HTMLUListElement.h" |
| #include "RenderObject.h" |
| |
| namespace WebCore { |
| |
| ModifySelectionListLevelCommand::ModifySelectionListLevelCommand(Document& document) |
| : CompositeEditCommand(document) |
| { |
| } |
| |
| bool ModifySelectionListLevelCommand::preservesTypingStyle() const |
| { |
| return true; |
| } |
| |
| // This needs to be static so it can be called by canIncreaseSelectionListLevel and canDecreaseSelectionListLevel |
| static bool getStartEndListChildren(const VisibleSelection& selection, Node*& start, Node*& end) |
| { |
| if (selection.isNone()) |
| return false; |
| |
| // start must be in a list child |
| Node* startListChild = enclosingListChild(selection.start().anchorNode()); |
| if (!startListChild || !startListChild->renderer()) |
| return false; |
| |
| // end must be in a list child |
| Node* endListChild = selection.isRange() ? enclosingListChild(selection.end().anchorNode()) : startListChild; |
| if (!endListChild || !endListChild->renderer()) |
| 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 |
| // - if the end is anywhere in a sublist lower than start, the whole sublist gets moved |
| // 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; |
| } |
| |
| // if the selection ends on a list item with a sublist, include the entire sublist |
| if (endListChild->renderer()->isListItem()) { |
| RenderObject* r = endListChild->renderer()->nextSibling(); |
| if (r && isListHTMLElement(r->node()) && r->node()->parentNode() == startListChild->parentNode()) |
| endListChild = r->node(); |
| } |
| |
| start = startListChild; |
| end = endListChild; |
| return true; |
| } |
| |
| void ModifySelectionListLevelCommand::insertSiblingNodeRangeBefore(Node* startNode, Node* endNode, Node* refNode) |
| { |
| RefPtr node = startNode; |
| while (node) { |
| RefPtr next = node->nextSibling(); |
| removeNode(*node); |
| insertNodeBefore(*node, *refNode); |
| |
| if (node == endNode) |
| return; |
| |
| node = next; |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| void ModifySelectionListLevelCommand::insertSiblingNodeRangeAfter(Node* startNode, Node* endNode, Node* refNode) |
| { |
| RefPtr node = startNode; |
| RefPtr refChild = refNode; |
| while (node) { |
| RefPtr next = node->nextSibling(); |
| removeNode(*node); |
| insertNodeAfter(*node, *refChild); |
| |
| if (node == endNode) |
| return; |
| |
| refChild = node; |
| node = next; |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| void ModifySelectionListLevelCommand::appendSiblingNodeRange(Node* startNode, Node* endNode, Element* newParent) |
| { |
| RefPtr node = startNode; |
| while (node) { |
| RefPtr next = node->nextSibling(); |
| removeNode(*node); |
| appendNode(*node, *newParent); |
| |
| if (node == endNode) |
| return; |
| |
| node = next; |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| IncreaseSelectionListLevelCommand::IncreaseSelectionListLevelCommand(Document& document, Type listType) |
| : ModifySelectionListLevelCommand(document) |
| , m_listType(listType) |
| { |
| } |
| |
| // This needs to be static so it can be called by canIncreaseSelectionListLevel |
| static bool canIncreaseListLevel(const VisibleSelection& selection, Node*& start, Node*& end) |
| { |
| if (!getStartEndListChildren(selection, start, end)) |
| return false; |
| |
| // start must not be the first child (because you need a prior one |
| // to increase relative to) |
| if (!start->renderer()->previousSibling()) |
| 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() |
| { |
| Node* startListChild; |
| Node* endListChild; |
| if (!canIncreaseListLevel(endingSelection(), startListChild, endListChild)) |
| return; |
| |
| Node* previousItem = startListChild->renderer()->previousSibling()->node(); |
| if (isListHTMLElement(previousItem)) { |
| // move nodes up into preceding list |
| appendSiblingNodeRange(startListChild, endListChild, downcast<Element>(previousItem)); |
| m_listElement = previousItem; |
| } else { |
| // create a sublist for the preceding element and move nodes there |
| RefPtr<Element> newParent; |
| switch (m_listType) { |
| case Type::InheritedListType: |
| newParent = startListChild->parentElement(); |
| if (newParent) |
| newParent = newParent->cloneElementWithoutChildren(document()); |
| break; |
| case Type::OrderedList: |
| newParent = HTMLOListElement::create(document()); |
| break; |
| case Type::UnorderedList: |
| newParent = HTMLUListElement::create(document()); |
| break; |
| } |
| insertNodeBefore(*newParent, *startListChild); |
| appendSiblingNodeRange(startListChild, endListChild, newParent.get()); |
| m_listElement = WTFMove(newParent); |
| } |
| } |
| |
| bool IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(Document* document) |
| { |
| Node* startListChild; |
| Node* endListChild; |
| return canIncreaseListLevel(document->frame()->selection().selection(), startListChild, endListChild); |
| } |
| |
| RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document, Type type) |
| { |
| ASSERT(document); |
| ASSERT(document->frame()); |
| auto command = create(*document, type); |
| command->apply(); |
| return WTFMove(command->m_listElement); |
| } |
| |
| RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document) |
| { |
| return increaseSelectionListLevel(document, Type::InheritedListType); |
| } |
| |
| RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(Document* document) |
| { |
| return increaseSelectionListLevel(document, Type::OrderedList); |
| } |
| |
| RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(Document* document) |
| { |
| return increaseSelectionListLevel(document, Type::UnorderedList); |
| } |
| |
| DecreaseSelectionListLevelCommand::DecreaseSelectionListLevelCommand(Document& document) |
| : ModifySelectionListLevelCommand(document) |
| { |
| } |
| |
| // This needs to be static so it can be called by canDecreaseSelectionListLevel |
| static bool canDecreaseListLevel(const VisibleSelection& selection, Node*& start, Node*& end) |
| { |
| if (!getStartEndListChildren(selection, start, end)) |
| return false; |
| |
| // there must be a destination list to move the items to |
| if (!isListHTMLElement(start->parentNode()->parentNode())) |
| return false; |
| |
| return true; |
| } |
| |
| void DecreaseSelectionListLevelCommand::doApply() |
| { |
| Node* startListChild; |
| Node* endListChild; |
| if (!canDecreaseListLevel(endingSelection(), startListChild, endListChild)) |
| return; |
| |
| Node* previousItem = startListChild->renderer()->previousSibling() ? startListChild->renderer()->previousSibling()->node() : 0; |
| Node* nextItem = endListChild->renderer()->nextSibling() ? endListChild->renderer()->nextSibling()->node() : 0; |
| Element* listNode = startListChild->parentElement(); |
| |
| 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 && listNode) |
| removeNode(*listNode); |
| } else if (!nextItem) { |
| // at end of list, move the child(ren) to after the sublist |
| insertSiblingNodeRangeAfter(startListChild, endListChild, listNode); |
| } else if (listNode) { |
| // in the middle of list, split the list and move the children to the divide |
| splitElement(*listNode, *startListChild); |
| insertSiblingNodeRangeBefore(startListChild, endListChild, listNode); |
| } |
| } |
| |
| bool DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(Document* document) |
| { |
| Node* startListChild; |
| Node* endListChild; |
| return canDecreaseListLevel(document->frame()->selection().selection(), startListChild, endListChild); |
| } |
| |
| void DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(Document* document) |
| { |
| ASSERT(document); |
| ASSERT(document->frame()); |
| create(*document)->apply(); |
| } |
| |
| } |