LayoutTests:
[WebKit-https.git] / WebCore / editing / InsertListCommand.cpp
1 /*
2  * Copyright (C) 2006 Apple Computer, 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 "Element.h"
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"
34
35 namespace WebCore {
36
37 using namespace HTMLNames;
38
39 Node* InsertListCommand::fixOrphanedListChild(Node* node)
40 {
41     RefPtr<Element> listElement = createUnorderedListElement(document());
42     insertNodeBefore(listElement.get(), node);
43     removeNode(node);
44     appendNode(node, listElement.get());
45     return listElement.get();
46 }
47
48 InsertListCommand::InsertListCommand(Document* document, EListType type, const String& id) 
49     : CompositeEditCommand(document), m_type(type), m_id(id), m_forceCreateList(false)
50 {}
51
52 bool InsertListCommand::modifyRange()
53 {
54     ASSERT(endingSelection().isRange());
55     VisiblePosition visibleStart = endingSelection().visibleStart();
56     VisiblePosition visibleEnd = endingSelection().visibleEnd();
57     VisiblePosition startOfLastParagraph = startOfParagraph(visibleEnd);
58     
59     if (startOfParagraph(visibleStart) == startOfLastParagraph)
60         return false;
61     
62     Node* startList = enclosingList(visibleStart.deepEquivalent().node());
63     Node* endList = enclosingList(visibleEnd.deepEquivalent().node());
64     if (!startList || startList != endList)
65         m_forceCreateList = true;
66
67     setEndingSelection(visibleStart);
68     doApply();
69     visibleStart = endingSelection().visibleStart();
70     VisiblePosition nextParagraph = endOfParagraph(visibleStart).next();
71     while (nextParagraph.isNotNull() && nextParagraph != startOfLastParagraph) {
72         setEndingSelection(nextParagraph);
73         doApply();
74         nextParagraph = endOfParagraph(endingSelection().visibleStart()).next();
75     }
76     setEndingSelection(visibleEnd);
77     doApply();
78     visibleEnd = endingSelection().visibleEnd();
79     setEndingSelection(Selection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM));
80     m_forceCreateList = false;
81     
82     return true;
83 }
84
85 void InsertListCommand::doApply()
86 {
87     if (endingSelection().isNone())
88         return;
89
90     if (endingSelection().isRange() && modifyRange())
91         return;
92     
93     if (!endingSelection().rootEditableElement())
94         return;
95     
96     Node* selectionNode = endingSelection().start().node();
97     const QualifiedName listTag = (m_type == OrderedListType) ? olTag : ulTag;
98     Node* listChildNode = enclosingListChild(selectionNode);
99     bool switchListType = false;
100     if (listChildNode) {
101         // Remove the list chlild.
102         Node* listNode = enclosingList(listChildNode);
103         if (!listNode)
104             listNode = fixOrphanedListChild(listChildNode);
105         if (!listNode->hasTagName(listTag))
106             // listChildNode will be removed from the list and a list of type m_type will be created.
107             switchListType = true;
108         Node* nextListChild;
109         Node* previousListChild;
110         VisiblePosition start;
111         VisiblePosition end;
112         if (listChildNode->hasTagName(liTag)) {
113             start = VisiblePosition(Position(listChildNode, 0));
114             end = VisiblePosition(Position(listChildNode, maxDeepOffset(listChildNode)));
115             nextListChild = listChildNode->nextSibling();
116             previousListChild = listChildNode->previousSibling();
117         } else {
118             // A paragraph is visually a list item minus a list marker.  The paragraph will be moved.
119             start = startOfParagraph(endingSelection().visibleStart());
120             end = endOfParagraph(endingSelection().visibleEnd());
121             nextListChild = enclosingListChild(end.next().deepEquivalent().node());
122             ASSERT(nextListChild != listChildNode);
123             if (enclosingList(nextListChild) != listNode)
124                 nextListChild = 0;
125             previousListChild = enclosingListChild(start.previous().deepEquivalent().node());
126             ASSERT(previousListChild != listChildNode);
127             if (enclosingList(previousListChild) != listNode)
128                 previousListChild = 0;
129         }
130         // When removing a list, we must always create a placeholder to act as a point of insertion
131         // for the list content being removed.
132         RefPtr<Element> placeholder = createBreakElement(document());
133         if (nextListChild && previousListChild) {
134             splitElement(static_cast<Element *>(listNode), nextListChild);
135             insertNodeBefore(placeholder.get(), listNode);
136         } else if (nextListChild)
137             insertNodeBefore(placeholder.get(), listNode);
138         else
139             insertNodeAfter(placeholder.get(), listNode);
140         VisiblePosition insertionPoint = VisiblePosition(Position(placeholder.get(), 0));
141         moveParagraphs(start, end, insertionPoint, true);
142     }
143     if (!listChildNode || switchListType || m_forceCreateList) {
144         // Create list.
145         VisiblePosition start = startOfParagraph(endingSelection().visibleStart());
146         VisiblePosition end = endOfParagraph(endingSelection().visibleEnd());
147         
148         // Check for adjoining lists.
149         VisiblePosition previousPosition = start.previous(true);
150         VisiblePosition nextPosition = end.next(true);
151         RefPtr<Element> listItemElement = createListItemElement(document());
152         Node* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node());
153         Node* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node());
154         if (previousList && !previousList->hasTagName(listTag))
155             previousList = 0;
156         if (nextList && !nextList->hasTagName(listTag))
157             nextList = 0;
158         // Stitch matching adjoining lists together.
159         if (previousList)
160             appendNode(listItemElement.get(), previousList);
161         else if (nextList)
162             appendNode(listItemElement.get(), nextList);
163         else {
164             // Create the list.
165             RefPtr<Element> listElement = m_type == OrderedListType ? createOrderedListElement(document()) : createUnorderedListElement(document());
166             static_cast<HTMLElement*>(listElement.get())->setId(m_id);
167             appendNode(listItemElement.get(), listElement.get());
168             insertNodeAt(listElement.get(), start.deepEquivalent().node(), start.deepEquivalent().offset());
169         }
170         moveParagraph(start, end, VisiblePosition(Position(listItemElement.get(), 0)), true);
171         if (nextList && previousList)
172             mergeIdenticalElements(static_cast<Element*>(previousList), static_cast<Element*>(nextList));
173     }
174 }
175
176 }