Commit 99e6fb4d authored by harrison's avatar harrison

Reviewed by Justin.

        <rdar://problem/4359736> Support outlining ability with lists

        Added Mail SPI for list level changes.  It is SPI because it is not complete support
        for outlining.  See <rdar://problem/4457070> "API for html lists as note outlines".
        Additional support is to end a list when return is typed on empty list item.

        * WebCore.vcproj/WebCore/WebCore.vcproj:
        * WebCore.xcodeproj/project.pbxproj:
        Added ModifySelectionListLevelCommand.cpp and ModifySelectionListLevelCommand.h
        
        * bridge/mac/WebCoreFrameBridge.h:
        * bridge/mac/WebCoreFrameBridge.mm:
        (-[WebCoreFrameBridge canIncreaseSelectionListLevel]):
        (-[WebCoreFrameBridge canDecreaseSelectionListLevel]):
        (-[WebCoreFrameBridge increaseSelectionListLevel]):
        (-[WebCoreFrameBridge decreaseSelectionListLevel]):
        Supply list level calls in the bridge.
        
        * editing/InsertParagraphSeparatorCommand.cpp:
        (WebCore::createListItemElement):
        New.  Creates an li.
        
        (WebCore::InsertParagraphSeparatorCommand::doApply):
        - slight logic cleanup
        - on empty list item, end the list
        
        * editing/ModifySelectionListLevelCommand.cpp: Added.
        (WebCore::ModifySelectionListLevelCommand::ModifySelectionListLevelCommand):
        (WebCore::ModifySelectionListLevelCommand::preservesTypingStyle):
        (WebCore::ModifySelectionListLevelCommand::doApply):
        (WebCore::ModifySelectionListLevelCommand::canIncreaseSelectionListLevel):
        (WebCore::ModifySelectionListLevelCommand::canDecreaseSelectionListLevel):
        (WebCore::ModifySelectionListLevelCommand::increaseSelectionListLevel):
        (WebCore::ModifySelectionListLevelCommand::decreaseSelectionListLevel):
        (WebCore::modifySelectionListLevel):
        (WebCore::getStartEndListChildren):
        (WebCore::canIncreaseListLevel):
        (WebCore::canDecreaseListLevel):
        (WebCore::ModifySelectionListLevelCommand::insertSiblingNodeRangeBefore):
        (WebCore::ModifySelectionListLevelCommand::insertSiblingNodeRangeAfter):
        (WebCore::ModifySelectionListLevelCommand::appendSiblingNodeRange):
        (WebCore::ModifySelectionListLevelCommand::increaseListLevel):
        (WebCore::ModifySelectionListLevelCommand::decreaseListLevel):
        * editing/ModifySelectionListLevelCommand.h: Added.
        New editing command for adjusting the list level.
        
        * editing/ReplaceSelectionCommand.cpp:
        (WebCore::ReplaceSelectionCommand::doApply):
        Slight logic cleanup

        * editing/htmlediting.cpp:
        (WebCore::isListElement):
        (WebCore::enclosingListChild):
        New helpers.
        
        (WebCore::isTableElement):
        Allow caller to pass 0... return false in that case.
        
        * editing/htmlediting.h:
        New helpers.
        
        * rendering/RenderContainer.cpp:
        (WebCore::updateListMarkerNumbers):
        Allow for list children that are not li nodes.  Ignore and continue rather than stopping.
        
        (WebCore::RenderContainer::addChild):
        Allow for nil beforeChild, meaning "add at end".



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@13075 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 029ee4ca
2006-02-23 David Harrison <harrison@apple.com>
Reviewed by Justin.
<rdar://problem/4359736> Support outlining ability with lists
Added Mail SPI for list level changes. It is SPI because it is not complete support
for outlining. See <rdar://problem/4457070> "API for html lists as note outlines".
Additional support is to end a list when return is typed on empty list item.
* WebCore.vcproj/WebCore/WebCore.vcproj:
* WebCore.xcodeproj/project.pbxproj:
Added ModifySelectionListLevelCommand.cpp and ModifySelectionListLevelCommand.h
* bridge/mac/WebCoreFrameBridge.h:
* bridge/mac/WebCoreFrameBridge.mm:
(-[WebCoreFrameBridge canIncreaseSelectionListLevel]):
(-[WebCoreFrameBridge canDecreaseSelectionListLevel]):
(-[WebCoreFrameBridge increaseSelectionListLevel]):
(-[WebCoreFrameBridge decreaseSelectionListLevel]):
Supply list level calls in the bridge.
* editing/InsertParagraphSeparatorCommand.cpp:
(WebCore::createListItemElement):
New. Creates an li.
(WebCore::InsertParagraphSeparatorCommand::doApply):
- slight logic cleanup
- on empty list item, end the list
* editing/ModifySelectionListLevelCommand.cpp: Added.
(WebCore::ModifySelectionListLevelCommand::ModifySelectionListLevelCommand):
(WebCore::ModifySelectionListLevelCommand::preservesTypingStyle):
(WebCore::ModifySelectionListLevelCommand::doApply):
(WebCore::ModifySelectionListLevelCommand::canIncreaseSelectionListLevel):
(WebCore::ModifySelectionListLevelCommand::canDecreaseSelectionListLevel):
(WebCore::ModifySelectionListLevelCommand::increaseSelectionListLevel):
(WebCore::ModifySelectionListLevelCommand::decreaseSelectionListLevel):
(WebCore::modifySelectionListLevel):
(WebCore::getStartEndListChildren):
(WebCore::canIncreaseListLevel):
(WebCore::canDecreaseListLevel):
(WebCore::ModifySelectionListLevelCommand::insertSiblingNodeRangeBefore):
(WebCore::ModifySelectionListLevelCommand::insertSiblingNodeRangeAfter):
(WebCore::ModifySelectionListLevelCommand::appendSiblingNodeRange):
(WebCore::ModifySelectionListLevelCommand::increaseListLevel):
(WebCore::ModifySelectionListLevelCommand::decreaseListLevel):
* editing/ModifySelectionListLevelCommand.h: Added.
New editing command for adjusting the list level.
* editing/ReplaceSelectionCommand.cpp:
(WebCore::ReplaceSelectionCommand::doApply):
Slight logic cleanup
* editing/htmlediting.cpp:
(WebCore::isListElement):
(WebCore::enclosingListChild):
New helpers.
(WebCore::isTableElement):
Allow caller to pass 0... return false in that case.
* editing/htmlediting.h:
New helpers.
* rendering/RenderContainer.cpp:
(WebCore::updateListMarkerNumbers):
Allow for list children that are not li nodes. Ignore and continue rather than stopping.
(WebCore::RenderContainer::addChild):
Allow for nil beforeChild, meaning "add at end".
2006-03-01 Eric Seidel <eseidel@apple.com>
Reviewed by justing.
......
......@@ -957,6 +957,14 @@
RelativePath="..\..\editing\MergeIdenticalElementsCommand.h"
>
</File>
<File
RelativePath="..\..\editing\ModifySelectionListLevelCommand.cpp"
>
</File>
<File
RelativePath="..\..\editing\ModifySelectionListLevelCommand.h"
>
</File>
<File
RelativePath="..\..\editing\MoveSelectionCommand.cpp"
>
......
......@@ -1033,6 +1033,8 @@
BCFB2F42097A24B500BA703D /* SegmentedString.h in Headers */ = {isa = PBXBuildFile; fileRef = BCFB2F40097A24B500BA703D /* SegmentedString.h */; };
BCFB2F76097A2E1A00BA703D /* Arena.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BCFB2F74097A2E1A00BA703D /* Arena.cpp */; };
BCFB2F77097A2E1A00BA703D /* Arena.h in Headers */ = {isa = PBXBuildFile; fileRef = BCFB2F75097A2E1A00BA703D /* Arena.h */; };
C6D74AD509AA282E000B0A52 /* ModifySelectionListLevelCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = C6D74AD309AA282E000B0A52 /* ModifySelectionListLevelCommand.h */; };
C6D74AE409AA290A000B0A52 /* ModifySelectionListLevelCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C6D74AE309AA290A000B0A52 /* ModifySelectionListLevelCommand.cpp */; };
DD763BB20992C2C900740B8E /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DD763BB10992C2C900740B8E /* libxml2.dylib */; };
E1EE773708F1086C00166870 /* WebCoreTextDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = E1EE773508F1086C00166870 /* WebCoreTextDecoder.h */; settings = {ATTRIBUTES = (Private, ); }; };
E1EE773808F1086C00166870 /* WebCoreTextDecoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = E1EE773608F1086C00166870 /* WebCoreTextDecoder.mm */; };
......@@ -2111,6 +2113,8 @@
BE983D95052A2E0A00892D85 /* WebCoreKeyboardAccess.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = WebCoreKeyboardAccess.h; sourceTree = "<group>"; tabWidth = 8; usesTabs = 0; };
BEF7EEA005FF8F0D009717EE /* KWQEditCommand.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = KWQEditCommand.h; sourceTree = "<group>"; tabWidth = 8; usesTabs = 0; };
BEF7EEA105FF8F0D009717EE /* KWQEditCommand.mm */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KWQEditCommand.mm; sourceTree = "<group>"; tabWidth = 8; usesTabs = 0; };
C6D74AD309AA282E000B0A52 /* ModifySelectionListLevelCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModifySelectionListLevelCommand.h; sourceTree = "<group>"; };
C6D74AE309AA290A000B0A52 /* ModifySelectionListLevelCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ModifySelectionListLevelCommand.cpp; sourceTree = "<group>"; };
DD763BB10992C2C900740B8E /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = /usr/lib/libxml2.dylib; sourceTree = "<absolute>"; };
E1EE773508F1086C00166870 /* WebCoreTextDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = WebCoreTextDecoder.h; sourceTree = "<group>"; tabWidth = 8; usesTabs = 0; };
E1EE773608F1086C00166870 /* WebCoreTextDecoder.mm */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebCoreTextDecoder.mm; sourceTree = "<group>"; tabWidth = 8; usesTabs = 0; };
......@@ -2563,6 +2567,8 @@
93309DA9099E64910056E581 /* markup.h */,
93309DAA099E64910056E581 /* MergeIdenticalElementsCommand.cpp */,
93309DAB099E64910056E581 /* MergeIdenticalElementsCommand.h */,
C6D74AE309AA290A000B0A52 /* ModifySelectionListLevelCommand.cpp */,
C6D74AD309AA282E000B0A52 /* ModifySelectionListLevelCommand.h */,
93309DAC099E64910056E581 /* MoveSelectionCommand.cpp */,
93309DAD099E64910056E581 /* MoveSelectionCommand.h */,
93309DAF099E64910056E581 /* RebalanceWhitespaceCommand.cpp */,
......@@ -4404,6 +4410,7 @@
935EB36209B2D7DB00F3AF5B /* TransferJobClient.h in Headers */,
A82398A609B3ACDB00B60641 /* PlugInInfoStore.h in Headers */,
A8239E0109B3CF8A00B60641 /* Logging.h in Headers */,
C6D74AD509AA282E000B0A52 /* ModifySelectionListLevelCommand.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -5520,6 +5527,7 @@
65F5386B09B2C05E00F3DC4A /* CharsetNames.cpp in Sources */,
A82398A809B3ACF500B60641 /* PlugInInfoStoreMac.mm in Sources */,
A8239E0009B3CF8A00B60641 /* Logging.cpp in Sources */,
C6D74AE409AA290A000B0A52 /* ModifySelectionListLevelCommand.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......
......@@ -29,10 +29,14 @@
#include "DocumentImpl.h"
#include "Logging.h"
#include "css_computedstyle.h"
#include "cssproperties.h"
#include "dom_elementimpl.h"
#include "dom_position.h"
#include "TextImpl.h"
#include "htmlediting.h"
#include "HTMLElementImpl.h"
#include "htmlnames.h"
#include "render_object.h"
#include "VisiblePosition.h"
#include "visible_units.h"
#include <kxmlcore/Assertions.h>
......@@ -76,6 +80,59 @@ void InsertParagraphSeparatorCommand::applyStyleAfterInsertion()
applyStyle(m_style.get());
}
PassRefPtr<ElementImpl> createListItemElement(DocumentImpl *document)
{
int exceptionCode = 0;
RefPtr<ElementImpl> breakNode = document->createElementNS(xhtmlNamespaceURI, "li", exceptionCode);
ASSERT(exceptionCode == 0);
return breakNode.release();
}
static NodeImpl* embeddedSublist(NodeImpl* listItem)
{
// check for sublist embedded in the list item
// NOTE: Must allow for collapsed sublist (i.e. no renderer), so just check DOM
for (NodeImpl* n = listItem->firstChild(); n; n = n->nextSibling()) {
if (isListElement(n))
return n;
}
return 0;
}
static NodeImpl* appendedSublist(NodeImpl* listItem)
{
// check for sublist between regular list items
// NOTE: Must allow for collapsed sublist (i.e. no renderer), so just check DOM
for (NodeImpl* n = listItem->nextSibling(); n; n = n->nextSibling()) {
if (isListElement(n))
return n;
if (n->renderer() && n->renderer()->isListItem())
return 0;
}
return 0;
}
static NodeImpl* enclosingEmptyListItem(const VisiblePosition& visiblePos)
{
// check that position is on a line by itself inside a list item
NodeImpl* listChildNode = enclosingListChild(visiblePos.deepEquivalent().node());
if (!listChildNode || !isStartOfLine(visiblePos) || !isEndOfLine(visiblePos))
return 0;
// check for sublist embedded in the list item
if (embeddedSublist(listChildNode))
return 0;
// check for sublist between regular list items
if (appendedSublist(listChildNode))
return 0;
return listChildNode;
}
void InsertParagraphSeparatorCommand::doApply()
{
bool splitText = false;
......@@ -94,53 +151,69 @@ void InsertParagraphSeparatorCommand::doApply()
affinity = endingSelection().affinity();
}
// Adjust the insertion position after the delete
pos = positionAvoidingSpecialElementBoundary(pos);
VisiblePosition visiblePos(pos, affinity);
calculateStyleBeforeInsertion(pos);
// Find the start block.
//---------------------------------------------------------------------
// Handle special case of typing return on an empty list item
NodeImpl *emptyListItem = enclosingEmptyListItem(visiblePos);
if (emptyListItem) {
NodeImpl *listNode = emptyListItem->parentNode();
RefPtr<NodeImpl> newBlock = isListElement(listNode->parentNode()) ? createListItemElement(document()) : createDefaultParagraphElement(document());
if (emptyListItem->renderer()->nextSibling()) {
if (emptyListItem->renderer()->previousSibling())
splitElement(static_cast<ElementImpl *>(listNode), emptyListItem);
insertNodeBefore(newBlock.get(), listNode);
removeNode(emptyListItem);
} else {
insertNodeAfter(newBlock.get(), listNode);
removeNode(emptyListItem->renderer()->previousSibling() ? emptyListItem : listNode);
}
appendBlockPlaceholder(newBlock.get());
setEndingSelection(Position(newBlock.get(), 0), DOWNSTREAM);
applyStyleAfterInsertion();
return;
}
//---------------------------------------------------------------------
// Prepare for more general cases.
NodeImpl *startNode = pos.node();
NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
if (!startBlock || !startBlock->parentNode())
return;
VisiblePosition visiblePos(pos, affinity);
bool isFirstInBlock = isStartOfBlock(visiblePos);
bool isLastInBlock = isEndOfBlock(visiblePos);
bool startBlockIsRoot = startBlock == startBlock->rootEditableElement();
// This is the block that is going to be inserted.
RefPtr<NodeImpl> blockToInsert = startBlockIsRoot
? static_pointer_cast<NodeImpl>(createDefaultParagraphElement(document()))
: startBlock->cloneNode(false);
//---------------------------------------------------------------------
// Handle empty block case.
if (isFirstInBlock && isLastInBlock) {
LOG(Editing, "insert paragraph separator: empty block case");
if (startBlockIsRoot) {
RefPtr<NodeImpl> extraBlock = createDefaultParagraphElement(document());
appendNode(extraBlock.get(), startBlock);
appendBlockPlaceholder(extraBlock.get());
appendNode(blockToInsert.get(), startBlock);
}
else {
insertNodeAfter(blockToInsert.get(), startBlock);
}
appendBlockPlaceholder(blockToInsert.get());
setEndingSelection(Position(blockToInsert.get(), 0), DOWNSTREAM);
applyStyleAfterInsertion();
return;
}
bool nestNewBlock = false;
// Create block to be inserted.
RefPtr<NodeImpl> blockToInsert;
if (startBlock == startBlock->rootEditableElement()) {
blockToInsert = static_pointer_cast<NodeImpl>(createDefaultParagraphElement(document()));
nestNewBlock = true;
} else
blockToInsert = startBlock->cloneNode(false);
//---------------------------------------------------------------------
// Handle case when position is in the last visible position in its block.
// Handle case when position is in the last visible position in its block,
// including when the block is empty.
if (isLastInBlock) {
LOG(Editing, "insert paragraph separator: last in block case");
if (startBlockIsRoot)
if (nestNewBlock) {
if (isFirstInBlock) {
// block is empty: create an empty paragraph to
// represent the content before the new one.
RefPtr<NodeImpl> extraBlock = createDefaultParagraphElement(document());
appendNode(extraBlock.get(), startBlock);
appendBlockPlaceholder(extraBlock.get());
}
appendNode(blockToInsert.get(), startBlock);
else
} else
insertNodeAfter(blockToInsert.get(), startBlock);
appendBlockPlaceholder(blockToInsert.get());
setEndingSelection(Position(blockToInsert.get(), 0), DOWNSTREAM);
applyStyleAfterInsertion();
......@@ -148,24 +221,19 @@ void InsertParagraphSeparatorCommand::doApply()
}
//---------------------------------------------------------------------
// Handle case when position is in the first visible position in its block.
// and similar case where upstream position is in another block.
bool prevInDifferentBlock = !inSameBlock(visiblePos, visiblePos.previous());
if (prevInDifferentBlock || isFirstInBlock) {
LOG(Editing, "insert paragraph separator: first in block case");
// Handle case when position is in the first visible position in its block, and
// similar case where previous position is in another, presumeably nested, block.
if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) {
pos = pos.downstream();
pos = positionOutsideContainingSpecialElement(pos);
Position refPos;
NodeImpl *refNode;
if (isFirstInBlock && !startBlockIsRoot) {
if (isFirstInBlock && !nestNewBlock)
refNode = startBlock;
} else if (pos.node() == startBlock && startBlockIsRoot) {
ASSERT(startBlock->childNode(pos.offset())); // must be true or we'd be in the end of block case
else if (pos.node() == startBlock && nestNewBlock) {
refNode = startBlock->childNode(pos.offset());
} else {
ASSERT(refNode); // must be true or we'd be in the end of block case
} else
refNode = pos.node();
}
insertNodeBefore(blockToInsert.get(), refNode);
appendBlockPlaceholder(blockToInsert.get());
......@@ -178,8 +246,6 @@ void InsertParagraphSeparatorCommand::doApply()
//---------------------------------------------------------------------
// Handle the (more complicated) general case,
LOG(Editing, "insert paragraph separator: general case");
// If pos.node() is a <br> and the document is in quirks mode, this <br>
// will collapse away when we add a block after it. Add an extra <br>.
if (!document()->inStrictMode()) {
......@@ -220,11 +286,10 @@ void InsertParagraphSeparatorCommand::doApply()
}
// Put the added block in the tree.
if (startBlockIsRoot) {
if (nestNewBlock)
appendNode(blockToInsert.get(), startBlock);
} else {
else
insertNodeAfter(blockToInsert.get(), startBlock);
}
// Make clones of ancestors in between the start node and the start block.
RefPtr<NodeImpl> parent = blockToInsert;
......@@ -244,9 +309,9 @@ void InsertParagraphSeparatorCommand::doApply()
// Move the start node and the siblings of the start node.
if (startNode != startBlock) {
NodeImpl *n = startNode;
if (pos.offset() >= startNode->caretMaxOffset()) {
if (pos.offset() >= startNode->caretMaxOffset())
n = startNode->nextSibling();
}
while (n && n != blockToInsert) {
NodeImpl *next = n->nextSibling();
removeNode(n);
......
/*
* Copyright (C) 2006 Apple Computer, 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 COMPUTER, 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 COMPUTER, 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 "ModifySelectionListLevelCommand.h"
#include "CompositeEditCommand.h"
#include "DocumentImpl.h"
#include "Frame.h"
#include "render_object.h"
#include "SelectionController.h"
#include "htmlediting.h"
#include <kxmlcore/Assertions.h>
namespace WebCore {
static bool canIncreaseListLevel(Selection selection, NodeImpl** start, NodeImpl** end);
static bool canDecreaseListLevel(Selection selection, NodeImpl** start, NodeImpl** end);
static void modifySelectionListLevel(DocumentImpl *document, EListLevelModification mod);
// public functions
ModifySelectionListLevelCommand::ModifySelectionListLevelCommand(DocumentImpl* document, EListLevelModification mod)
: CompositeEditCommand(document)
{
m_modification = mod;
}
bool ModifySelectionListLevelCommand::preservesTypingStyle() const
{
return true;
}
void ModifySelectionListLevelCommand::doApply()
{
if (m_modification == IncreaseListLevel)
increaseListLevel(endingSelection());
else if (m_modification == DecreaseListLevel)
decreaseListLevel(endingSelection());
}
bool ModifySelectionListLevelCommand::canIncreaseSelectionListLevel(DocumentImpl *document)
{
NodeImpl* startListChild;
NodeImpl* endListChild;
return canIncreaseListLevel(document->frame()->selection().selection(), &startListChild, &endListChild);
}
bool ModifySelectionListLevelCommand::canDecreaseSelectionListLevel(DocumentImpl *document)
{
NodeImpl* startListChild;
NodeImpl* endListChild;
return canDecreaseListLevel(document->frame()->selection().selection(), &startListChild, &endListChild);
}
void ModifySelectionListLevelCommand::increaseSelectionListLevel(DocumentImpl *document)
{
modifySelectionListLevel(document, IncreaseListLevel);
}
void ModifySelectionListLevelCommand::decreaseSelectionListLevel(DocumentImpl *document)
{
modifySelectionListLevel(document, DecreaseListLevel);
}
// private functions
static void modifySelectionListLevel(DocumentImpl *document, EListLevelModification mod)
{
ASSERT(document);
ASSERT(document->frame());
ModifySelectionListLevelCommand *modCommand = new ModifySelectionListLevelCommand(document, mod);
EditCommandPtr cmd(modCommand);
cmd.apply();
}
static bool getStartEndListChildren(Selection selection, NodeImpl** start, NodeImpl** end)
{
if (selection.isNone())
return false;
// start must be in a list child
NodeImpl* startListChild = enclosingListChild(selection.start().node());
if (!startListChild)
return false;
// end must be in a list child
NodeImpl* endListChild = selection.isRange() ? enclosingListChild(selection.end().node()) : startListChild;
if (!endListChild)
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 get 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
// This loop satisfies both of these requirements. Our utility functions that move sibling ranges
// take care of the rest.
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 sublist
if (endListChild->renderer()->isListItem()) {
RenderObject* r = endListChild->renderer()->nextSibling();
if (r && isListElement(r->element()))
endListChild = r->element();
}
*start = startListChild;
*end = endListChild;
return true;
}
static bool canIncreaseListLevel(Selection selection, NodeImpl** start, NodeImpl** 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;
}
static bool canDecreaseListLevel(Selection selection, NodeImpl** start, NodeImpl** end)
{
if (!getStartEndListChildren(selection, start, end))
return false;
// there must be a destination list to move the items to
if (!isListElement((*start)->parentNode()->parentNode()))
return false;
return true;
}
void ModifySelectionListLevelCommand::insertSiblingNodeRangeBefore(NodeImpl* startNode, NodeImpl* endNode, NodeImpl* refNode)
{
NodeImpl* node = startNode;
while (1) {
NodeImpl* next = node->nextSibling();
removeNode(node);
insertNodeBefore(node, refNode);
if (node == endNode)
break;
node = next;
}
}
void ModifySelectionListLevelCommand::insertSiblingNodeRangeAfter(NodeImpl* startNode, NodeImpl* endNode, NodeImpl* refNode)
{
NodeImpl* node = startNode;
while (1) {
NodeImpl* next = node->nextSibling();
removeNode(node);
insertNodeAfter(node, refNode);
if (node == endNode)
break;
refNode = node;
node = next;
}
}
void ModifySelectionListLevelCommand::appendSiblingNodeRange(NodeImpl* startNode, NodeImpl* endNode, NodeImpl* newParent)
{
NodeImpl* node = startNode;
while (1) {
NodeImpl* next = node->nextSibling();
removeNode(node);
appendNode(node, newParent);
if (node == endNode)
break;
node = next;
}
}
void ModifySelectionListLevelCommand::increaseListLevel(Selection selection)
{
NodeImpl* startListChild;
NodeImpl* endListChild;
if (!canIncreaseListLevel(selection, &startListChild, &endListChild))
return;
NodeImpl* previousItem = startListChild->renderer()->previousSibling()->element();
if (isListElement(previousItem)) {
// move nodes up into preceding list
appendSiblingNodeRange(startListChild, endListChild, previousItem);
} else {
// create a sublist for the preceding element and move nodes there
RefPtr<NodeImpl> newParent = startListChild->parentNode()->cloneNode(false);
insertNodeBefore(newParent.get(), startListChild);
appendSiblingNodeRange(startListChild, endListChild, newParent.get());
}
}
void ModifySelectionListLevelCommand::decreaseListLevel(Selection selection)
{
NodeImpl* startListChild;
NodeImpl* endListChild;
if (!canDecreaseListLevel(selection, &startListChild, &endListChild))
return;
NodeImpl* previousItem = startListChild->renderer()->previousSibling() ? startListChild->renderer()->previousSibling()->element() : 0;
NodeImpl* nextItem = endListChild->renderer()->nextSibling() ? endListChild->renderer()->nextSibling()->element() : 0;
NodeImpl* listNode = startListChild->parentNode();
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)
removeNode(listNode);
} else if (!nextItem) {