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, Type type, const String& id) 
49     : CompositeEditCommand(document), m_type(type), m_id(id), m_forceCreateList(false)
50 {
51 }
52
53 bool InsertListCommand::modifyRange()
54 {
55     ASSERT(endingSelection().isRange());
56     VisiblePosition visibleStart = endingSelection().visibleStart();
57     VisiblePosition visibleEnd = endingSelection().visibleEnd();
58     VisiblePosition startOfLastParagraph = startOfParagraph(visibleEnd);
59     
60     if (startOfParagraph(visibleStart) == startOfLastParagraph)
61         return false;
62     
63     Node* startList = enclosingList(visibleStart.deepEquivalent().node());
64     Node* endList = enclosingList(visibleEnd.deepEquivalent().node());
65     if (!startList || startList != endList)
66         m_forceCreateList = true;
67
68     setEndingSelection(visibleStart);
69     doApply();
70     visibleStart = endingSelection().visibleStart();
71     VisiblePosition nextParagraph = endOfParagraph(visibleStart).next();
72     while (nextParagraph.isNotNull() && nextParagraph != startOfLastParagraph) {
73         setEndingSelection(nextParagraph);
74         doApply();
75         nextParagraph = endOfParagraph(endingSelection().visibleStart()).next();
76     }
77     setEndingSelection(visibleEnd);
78     doApply();
79     visibleEnd = endingSelection().visibleEnd();
80     setEndingSelection(Selection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM));
81     m_forceCreateList = false;
82     
83     return true;
84 }
85
86 void InsertListCommand::doApply()
87 {
88     if (endingSelection().isNone())
89         return;
90
91     if (endingSelection().isRange() && modifyRange())
92         return;
93     
94     if (!endingSelection().rootEditableElement())
95         return;
96     
97     Node* selectionNode = endingSelection().start().node();
98     const QualifiedName listTag = (m_type == OrderedList) ? olTag : ulTag;
99     Node* listChildNode = enclosingListChild(selectionNode);
100     bool switchListType = false;
101     if (listChildNode) {
102         // Remove the list chlild.
103         Node* listNode = enclosingList(listChildNode);
104         if (!listNode)
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;
109         Node* nextListChild;
110         Node* previousListChild;
111         VisiblePosition start;
112         VisiblePosition end;
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();
118         } else {
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)
125                 nextListChild = 0;
126             previousListChild = enclosingListChild(start.previous().deepEquivalent().node());
127             ASSERT(previousListChild != listChildNode);
128             if (enclosingList(previousListChild) != listNode)
129                 previousListChild = 0;
130         }
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         if (nextListChild && previousListChild) {
135             splitElement(static_cast<Element *>(listNode), nextListChild);
136             insertNodeBefore(placeholder.get(), listNode);
137         } else if (nextListChild)
138             insertNodeBefore(placeholder.get(), listNode);
139         else
140             insertNodeAfter(placeholder.get(), listNode);
141         VisiblePosition insertionPoint = VisiblePosition(Position(placeholder.get(), 0));
142         moveParagraphs(start, end, insertionPoint, true);
143     }
144     if (!listChildNode || switchListType || m_forceCreateList) {
145         // Create list.
146         VisiblePosition start = startOfParagraph(endingSelection().visibleStart());
147         VisiblePosition end = endOfParagraph(endingSelection().visibleEnd());
148         
149         // Check for adjoining lists.
150         VisiblePosition previousPosition = start.previous(true);
151         VisiblePosition nextPosition = end.next(true);
152         RefPtr<Element> listItemElement = createListItemElement(document());
153         Node* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node());
154         Node* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node());
155         if (previousList && !previousList->hasTagName(listTag))
156             previousList = 0;
157         if (nextList && !nextList->hasTagName(listTag))
158             nextList = 0;
159         // Stitch matching adjoining lists together.
160         if (previousList)
161             appendNode(listItemElement.get(), previousList);
162         else if (nextList)
163             appendNode(listItemElement.get(), nextList);
164         else {
165             // Create the list.
166             RefPtr<Element> listElement = m_type == OrderedList ? createOrderedListElement(document()) : createUnorderedListElement(document());
167             static_cast<HTMLElement*>(listElement.get())->setId(m_id);
168             appendNode(listItemElement.get(), listElement.get());
169             insertNodeAt(listElement.get(), start.deepEquivalent().node(), start.deepEquivalent().offset());
170         }
171         moveParagraph(start, end, VisiblePosition(Position(listItemElement.get(), 0)), true);
172         if (nextList && previousList)
173             mergeIdenticalElements(static_cast<Element*>(previousList), static_cast<Element*>(nextList));
174     }
175 }
176
177 }