297 lines
11 KiB
C++
297 lines
11 KiB
C++
/*
|
|
* 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 "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)
|
|
{
|
|
Node* node = startNode;
|
|
while (1) {
|
|
Node* next = node->nextSibling();
|
|
removeNode(*node);
|
|
insertNodeBefore(*node, *refNode);
|
|
|
|
if (node == endNode)
|
|
break;
|
|
|
|
node = next;
|
|
}
|
|
}
|
|
|
|
void ModifySelectionListLevelCommand::insertSiblingNodeRangeAfter(Node* startNode, Node* endNode, Node* refNode)
|
|
{
|
|
Node* node = startNode;
|
|
while (1) {
|
|
Node* next = node->nextSibling();
|
|
removeNode(*node);
|
|
insertNodeAfter(*node, *refNode);
|
|
|
|
if (node == endNode)
|
|
break;
|
|
|
|
refNode = node;
|
|
node = next;
|
|
}
|
|
}
|
|
|
|
void ModifySelectionListLevelCommand::appendSiblingNodeRange(Node* startNode, Node* endNode, Element* newParent)
|
|
{
|
|
Node* node = startNode;
|
|
while (1) {
|
|
Node* next = node->nextSibling();
|
|
removeNode(*node);
|
|
appendNode(*node, *newParent);
|
|
|
|
if (node == endNode)
|
|
break;
|
|
|
|
node = next;
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
}
|