2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
28 #include "InsertListCommand.h"
29 #include "DocumentFragment.h"
30 #include "htmlediting.h"
31 #include "HTMLElement.h"
32 #include "HTMLNames.h"
33 #include "visible_units.h"
37 using namespace HTMLNames;
39 Node* InsertListCommand::fixOrphanedListChild(Node* node)
41 RefPtr<Element> listElement = createUnorderedListElement(document());
42 insertNodeBefore(listElement.get(), node);
44 appendNode(node, listElement.get());
45 return listElement.get();
48 InsertListCommand::InsertListCommand(Document* document, Type type, const String& id)
49 : CompositeEditCommand(document), m_type(type), m_id(id), m_forceCreateList(false)
53 bool InsertListCommand::modifyRange()
55 ASSERT(endingSelection().isRange());
56 VisiblePosition visibleStart = endingSelection().visibleStart();
57 VisiblePosition visibleEnd = endingSelection().visibleEnd();
58 VisiblePosition startOfLastParagraph = startOfParagraph(visibleEnd);
60 if (startOfParagraph(visibleStart) == startOfLastParagraph)
63 Node* startList = enclosingList(visibleStart.deepEquivalent().node());
64 Node* endList = enclosingList(visibleEnd.deepEquivalent().node());
65 if (!startList || startList != endList)
66 m_forceCreateList = true;
68 setEndingSelection(visibleStart);
70 visibleStart = endingSelection().visibleStart();
71 VisiblePosition nextParagraph = endOfParagraph(visibleStart).next();
72 while (nextParagraph.isNotNull() && nextParagraph != startOfLastParagraph) {
73 setEndingSelection(nextParagraph);
75 nextParagraph = endOfParagraph(endingSelection().visibleStart()).next();
77 setEndingSelection(visibleEnd);
79 visibleEnd = endingSelection().visibleEnd();
80 setEndingSelection(Selection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM));
81 m_forceCreateList = false;
86 void InsertListCommand::doApply()
88 if (endingSelection().isNone())
91 if (endingSelection().isRange() && modifyRange())
94 if (!endingSelection().rootEditableElement())
97 Node* selectionNode = endingSelection().start().node();
98 const QualifiedName listTag = (m_type == OrderedList) ? olTag : ulTag;
99 Node* listChildNode = enclosingListChild(selectionNode);
100 bool switchListType = false;
102 // Remove the list chlild.
103 Node* listNode = enclosingList(listChildNode);
105 listNode = fixOrphanedListChild(listChildNode);
106 if (!listNode->hasTagName(listTag))
107 // listChildNode will be removed from the list and a list of type m_type will be created.
108 switchListType = true;
110 Node* previousListChild;
111 VisiblePosition start;
113 if (listChildNode->hasTagName(liTag)) {
114 start = VisiblePosition(Position(listChildNode, 0));
115 end = VisiblePosition(Position(listChildNode, maxDeepOffset(listChildNode)));
116 nextListChild = listChildNode->nextSibling();
117 previousListChild = listChildNode->previousSibling();
119 // A paragraph is visually a list item minus a list marker. The paragraph will be moved.
120 start = startOfParagraph(endingSelection().visibleStart());
121 end = endOfParagraph(endingSelection().visibleEnd());
122 nextListChild = enclosingListChild(end.next().deepEquivalent().node());
123 ASSERT(nextListChild != listChildNode);
124 if (enclosingList(nextListChild) != listNode)
126 previousListChild = enclosingListChild(start.previous().deepEquivalent().node());
127 ASSERT(previousListChild != listChildNode);
128 if (enclosingList(previousListChild) != listNode)
129 previousListChild = 0;
131 // When removing a list, we must always create a placeholder to act as a point of insertion
132 // for the list content being removed.
133 RefPtr<Element> placeholder = createBreakElement(document());
134 RefPtr<Node> nodeToInsert = placeholder;
135 // If the content of the list item will be moved into another list, put it in a list item
136 // so that we don't create an orphaned list child.
137 if (enclosingList(listNode)) {
138 nodeToInsert = createListItemElement(document());
139 appendNode(placeholder.get(), nodeToInsert.get());
141 if (nextListChild && previousListChild) {
142 splitElement(static_cast<Element *>(listNode), nextListChild);
143 insertNodeBefore(nodeToInsert.get(), listNode);
144 } else if (nextListChild)
145 insertNodeBefore(nodeToInsert.get(), listNode);
147 insertNodeAfter(nodeToInsert.get(), listNode);
148 VisiblePosition insertionPoint = VisiblePosition(Position(placeholder.get(), 0));
149 moveParagraphs(start, end, insertionPoint, true);
151 if (!listChildNode || switchListType || m_forceCreateList) {
153 VisiblePosition start = startOfParagraph(endingSelection().visibleStart());
154 VisiblePosition end = endOfParagraph(endingSelection().visibleEnd());
156 // Check for adjoining lists.
157 VisiblePosition previousPosition = start.previous(true);
158 VisiblePosition nextPosition = end.next(true);
159 RefPtr<Element> listItemElement = createListItemElement(document());
160 Node* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node());
161 Node* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node());
162 if (previousList && !previousList->hasTagName(listTag))
164 if (nextList && !nextList->hasTagName(listTag))
166 // Stitch matching adjoining lists together.
168 appendNode(listItemElement.get(), previousList);
170 appendNode(listItemElement.get(), nextList);
173 RefPtr<Element> listElement = m_type == OrderedList ? createOrderedListElement(document()) : createUnorderedListElement(document());
174 static_cast<HTMLElement*>(listElement.get())->setId(m_id);
175 appendNode(listItemElement.get(), listElement.get());
176 insertNodeAt(listElement.get(), start.deepEquivalent().node(), start.deepEquivalent().offset());
178 moveParagraph(start, end, VisiblePosition(Position(listItemElement.get(), 0)), true);
179 if (nextList && previousList)
180 mergeIdenticalElements(static_cast<Element*>(previousList), static_cast<Element*>(nextList));