093b637fa1fd12e8f583ac94b1eeccad1d7de809
[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 PassRefPtr<Node> InsertListCommand::insertList(Document* document, Type type)
40 {
41     RefPtr<InsertListCommand> insertCommand = new InsertListCommand(document, type, "");
42     insertCommand->apply();
43     return insertCommand->m_listElement;
44 }
45
46 Node* InsertListCommand::fixOrphanedListChild(Node* node)
47 {
48     RefPtr<Element> listElement = createUnorderedListElement(document());
49     insertNodeBefore(listElement.get(), node);
50     removeNode(node);
51     appendNode(node, listElement.get());
52     m_listElement = listElement;
53     return listElement.get();
54 }
55
56 InsertListCommand::InsertListCommand(Document* document, Type type, const String& id) 
57     : CompositeEditCommand(document), m_type(type), m_id(id), m_forceCreateList(false)
58 {
59 }
60
61 bool InsertListCommand::modifyRange()
62 {
63     ASSERT(endingSelection().isRange());
64     VisiblePosition visibleStart = endingSelection().visibleStart();
65     VisiblePosition visibleEnd = endingSelection().visibleEnd();
66     VisiblePosition startOfLastParagraph = startOfParagraph(visibleEnd);
67     
68     // If the end of the selection to modify is just after a table, and
69     // if the start of the selection is inside that table, the last paragraph
70     // that we'll want modify is the last one inside the table, not the table itself.
71     // Adjust startOfLastParagraph here to avoid infinite recursion.
72     if (Node* table = isFirstPositionAfterTable(visibleEnd))
73         if (visibleStart.deepEquivalent().node()->isDescendantOf(table))
74             startOfLastParagraph = startOfParagraph(visibleEnd.previous(true));
75         
76     if (startOfParagraph(visibleStart) == startOfLastParagraph)
77         return false;
78     
79     Node* startList = enclosingList(visibleStart.deepEquivalent().node());
80     Node* endList = enclosingList(visibleEnd.deepEquivalent().node());
81     if (!startList || startList != endList)
82         m_forceCreateList = true;
83
84     setEndingSelection(visibleStart);
85     doApply();
86     visibleStart = endingSelection().visibleStart();
87     VisiblePosition nextParagraph = endOfParagraph(visibleStart).next();
88     while (nextParagraph.isNotNull() && nextParagraph != startOfLastParagraph) {
89         setEndingSelection(nextParagraph);
90         doApply();
91         nextParagraph = endOfParagraph(endingSelection().visibleStart()).next();
92     }
93     setEndingSelection(visibleEnd);
94     doApply();
95     visibleEnd = endingSelection().visibleEnd();
96     setEndingSelection(Selection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM));
97     m_forceCreateList = false;
98     
99     return true;
100 }
101
102 void InsertListCommand::doApply()
103 {
104     if (endingSelection().isNone())
105         return;
106     
107     if (!endingSelection().rootEditableElement())
108         return;
109     
110     VisiblePosition visibleEnd = endingSelection().visibleEnd();
111     VisiblePosition visibleStart = endingSelection().visibleStart();
112     // When a selection ends at the start of a paragraph, we rarely paint 
113     // the selection gap before that paragraph, because there often is no gap.  
114     // In a case like this, it's not obvious to the user that the selection 
115     // ends "inside" that paragraph, so it would be confusing if InsertUn{Ordered}List 
116     // operated on that paragraph.
117     // FIXME: We paint the gap before some paragraphs that are indented with left 
118     // margin/padding, but not others.  We should make the gap painting more consistent and 
119     // then use a left margin/padding rule here.
120     if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd))
121         setEndingSelection(Selection(visibleStart, visibleEnd.previous(true)));
122
123     if (endingSelection().isRange() && modifyRange())
124         return;
125     
126     Node* selectionNode = endingSelection().start().node();
127     const QualifiedName listTag = (m_type == OrderedList) ? olTag : ulTag;
128     Node* listChildNode = enclosingListChild(selectionNode);
129     bool switchListType = false;
130     if (listChildNode) {
131         // Remove the list chlild.
132         Node* listNode = enclosingList(listChildNode);
133         if (!listNode)
134             listNode = fixOrphanedListChild(listChildNode);
135         if (!listNode->hasTagName(listTag))
136             // listChildNode will be removed from the list and a list of type m_type will be created.
137             switchListType = true;
138         Node* nextListChild;
139         Node* previousListChild;
140         VisiblePosition start;
141         VisiblePosition end;
142         if (listChildNode->hasTagName(liTag)) {
143             start = VisiblePosition(Position(listChildNode, 0));
144             end = VisiblePosition(Position(listChildNode, maxDeepOffset(listChildNode)));
145             nextListChild = listChildNode->nextSibling();
146             previousListChild = listChildNode->previousSibling();
147         } else {
148             // A paragraph is visually a list item minus a list marker.  The paragraph will be moved.
149             start = startOfParagraph(endingSelection().visibleStart());
150             end = endOfParagraph(endingSelection().visibleEnd());
151             nextListChild = enclosingListChild(end.next().deepEquivalent().node());
152             ASSERT(nextListChild != listChildNode);
153             if (enclosingList(nextListChild) != listNode)
154                 nextListChild = 0;
155             previousListChild = enclosingListChild(start.previous().deepEquivalent().node());
156             ASSERT(previousListChild != listChildNode);
157             if (enclosingList(previousListChild) != listNode)
158                 previousListChild = 0;
159         }
160         // When removing a list, we must always create a placeholder to act as a point of insertion
161         // for the list content being removed.
162         RefPtr<Element> placeholder = createBreakElement(document());
163         RefPtr<Node> nodeToInsert = placeholder;
164         // If the content of the list item will be moved into another list, put it in a list item
165         // so that we don't create an orphaned list child.
166         if (enclosingList(listNode)) {
167             nodeToInsert = createListItemElement(document());
168             appendNode(placeholder.get(), nodeToInsert.get());
169         }
170         if (nextListChild && previousListChild) {
171             splitElement(static_cast<Element *>(listNode), nextListChild);
172             insertNodeBefore(nodeToInsert.get(), listNode);
173         } else if (nextListChild)
174             insertNodeBefore(nodeToInsert.get(), listNode);
175         else
176             insertNodeAfter(nodeToInsert.get(), listNode);
177         VisiblePosition insertionPoint = VisiblePosition(Position(placeholder.get(), 0));
178         moveParagraphs(start, end, insertionPoint, true);
179     }
180     if (!listChildNode || switchListType || m_forceCreateList) {
181         // Create list.
182         VisiblePosition start = startOfParagraph(endingSelection().visibleStart());
183         VisiblePosition end = endOfParagraph(endingSelection().visibleEnd());
184         
185         // Check for adjoining lists.
186         VisiblePosition previousPosition = start.previous(true);
187         VisiblePosition nextPosition = end.next(true);
188         RefPtr<Element> listItemElement = createListItemElement(document());
189         RefPtr<Element> placeholder = createBreakElement(document());
190         appendNode(placeholder.get(), listItemElement.get());
191         Node* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node());
192         Node* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node());
193         Node* startNode = start.deepEquivalent().node();
194         if (previousList && (!previousList->hasTagName(listTag) || startNode->isDescendantOf(previousList)))
195             previousList = 0;
196         if (nextList && (!nextList->hasTagName(listTag) || startNode->isDescendantOf(nextList)))
197             nextList = 0;
198         // Place list item into adjoining lists.
199         if (previousList)
200             appendNode(listItemElement.get(), previousList);
201         else if (nextList)
202             insertNodeAt(listItemElement.get(), Position(nextList, 0));
203         else {
204             // Create the list.
205             RefPtr<Element> listElement = m_type == OrderedList ? createOrderedListElement(document()) : createUnorderedListElement(document());
206             m_listElement = listElement;
207             if (!m_id.isEmpty())
208                 static_cast<HTMLElement*>(listElement.get())->setId(m_id);
209             appendNode(listItemElement.get(), listElement.get());
210             
211             if (start == end && isBlock(start.deepEquivalent().node())) {
212                 // Inserting the list into an empty paragraph that isn't held open 
213                 // by a br or a '\n', will invalidate start and end.  Insert 
214                 // a placeholder and then recompute start and end.
215                 Node* placeholder = insertBlockPlaceholder(start.deepEquivalent());
216                 start = VisiblePosition(Position(placeholder, 0));
217                 end = start;
218             }
219             
220             // Insert the list at a position visually equivalent to start of the
221             // paragraph that is being moved into the list. 
222             // Try to avoid inserting it somewhere where it will be surrounded by 
223             // inline ancestors of start, since it is easier for editing to produce 
224             // clean markup when inline elements are pushed down as far as possible.
225             Position insertionPos(start.deepEquivalent().upstream());
226             // Also avoid the containing list item.
227             Node* listChild = enclosingListChild(insertionPos.node());
228             if (listChild && listChild->hasTagName(liTag))
229                 insertionPos = positionBeforeNode(listChild);
230                 
231             insertNodeAt(listElement.get(), insertionPos);
232         }
233         moveParagraph(start, end, VisiblePosition(Position(placeholder.get(), 0)), true);
234         if (nextList && previousList)
235             mergeIdenticalElements(static_cast<Element*>(previousList), static_cast<Element*>(nextList));
236     }
237 }
238
239 }