Reviewed by adele
[WebKit-https.git] / WebCore / editing / CompositeEditCommand.cpp
1 /*
2  * Copyright (C) 2005, 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 "CompositeEditCommand.h"
28
29 #include "DocumentImpl.h"
30 #include "InlineTextBox.h"
31 #include "AppendNodeCommand.h"
32 #include "ApplyStyleCommand.h"
33 #include "DeleteFromTextNodeCommand.h"
34 #include "DeleteSelectionCommand.h"
35 #include "dom2_rangeimpl.h"
36 #include "htmlediting.h"
37 #include "htmlnames.h"
38 #include "InsertIntoTextNodeCommand.h"
39 #include "InsertNodeBeforeCommand.h"
40 #include "InsertParagraphSeparatorCommand.h"
41 #include "InsertTextCommand.h"
42 #include "JoinTextNodesCommand.h"
43 #include "MergeIdenticalElementsCommand.h"
44 #include "RebalanceWhitespaceCommand.h"
45 #include "RemoveCSSPropertyCommand.h"
46 #include "RemoveNodeAttributeCommand.h"
47 #include "RemoveNodeCommand.h"
48 #include "RemoveNodePreservingChildrenCommand.h"
49 #include "SetNodeAttributeCommand.h"
50 #include "SplitElementCommand.h"
51 #include "SplitTextNodeCommand.h"
52 #include "SplitTextNodeContainingElementCommand.h"
53 #include "VisiblePosition.h"
54 #include "visible_units.h"
55 #include "WrapContentsInDummySpanCommand.h"
56 #include <kxmlcore/Assertions.h>
57
58 namespace WebCore {
59
60 using namespace HTMLNames;
61
62 static const DOMString &blockPlaceholderClassString();
63
64 CompositeEditCommand::CompositeEditCommand(DocumentImpl *document) 
65     : EditCommand(document)
66 {
67 }
68
69 void CompositeEditCommand::doUnapply()
70 {
71     if (m_cmds.count() == 0)
72         return;
73     
74     QValueList<EditCommandPtr>::ConstIterator end;
75     for (QValueList<EditCommandPtr>::ConstIterator it = m_cmds.fromLast(); it != end; --it)
76         (*it)->unapply();
77
78     setState(NotApplied);
79 }
80
81 void CompositeEditCommand::doReapply()
82 {
83     if (m_cmds.count() == 0)
84         return;
85
86     for (QValueList<EditCommandPtr>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
87         (*it)->reapply();
88
89     setState(Applied);
90 }
91
92 //
93 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
94 //
95 void CompositeEditCommand::applyCommandToComposite(EditCommandPtr &cmd)
96 {
97     cmd.setStartingSelection(endingSelection());
98     cmd.setEndingSelection(endingSelection());
99     cmd.setParent(this);
100     cmd.apply();
101     m_cmds.append(cmd);
102 }
103
104 void CompositeEditCommand::applyStyle(CSSStyleDeclarationImpl *style, EditAction editingAction)
105 {
106     EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction));
107     applyCommandToComposite(cmd);
108 }
109
110 void CompositeEditCommand::applyStyle(CSSStyleDeclarationImpl *style, Position start, Position end, EditAction editingAction)
111 {
112     EditCommandPtr cmd(new ApplyStyleCommand(document(), style, start, end, editingAction));
113     applyCommandToComposite(cmd);
114 }
115
116 void CompositeEditCommand::insertParagraphSeparator()
117 {
118     EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
119     applyCommandToComposite(cmd);
120 }
121
122 void CompositeEditCommand::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
123 {
124     ASSERT(!refChild->hasTagName(bodyTag));
125     EditCommandPtr cmd(new InsertNodeBeforeCommand(document(), insertChild, refChild));
126     applyCommandToComposite(cmd);
127 }
128
129 void CompositeEditCommand::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
130 {
131     ASSERT(!refChild->hasTagName(bodyTag));
132     if (refChild->parentNode()->lastChild() == refChild) {
133         appendNode(insertChild, refChild->parentNode());
134     }
135     else {
136         ASSERT(refChild->nextSibling());
137         insertNodeBefore(insertChild, refChild->nextSibling());
138     }
139 }
140
141 void CompositeEditCommand::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, int offset)
142 {
143     if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
144         NodeImpl *child = refChild->firstChild();
145         for (int i = 0; child && i < offset; i++)
146             child = child->nextSibling();
147         if (child)
148             insertNodeBefore(insertChild, child);
149         else
150             appendNode(insertChild, refChild);
151     } 
152     else if (refChild->caretMinOffset() >= offset) {
153         insertNodeBefore(insertChild, refChild);
154     } 
155     else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
156         splitTextNode(static_cast<TextImpl *>(refChild), offset);
157         insertNodeBefore(insertChild, refChild);
158     } 
159     else {
160         insertNodeAfter(insertChild, refChild);
161     }
162 }
163
164 void CompositeEditCommand::appendNode(NodeImpl *appendChild, NodeImpl *parent)
165 {
166     EditCommandPtr cmd(new AppendNodeCommand(document(), appendChild, parent));
167     applyCommandToComposite(cmd);
168 }
169
170 void CompositeEditCommand::removeFullySelectedNode(NodeImpl *node)
171 {
172     if (isTableStructureNode(node) || node == node->rootEditableElement()) {
173         // Do not remove an element of table structure; remove its contents.
174         // Likewise for the root editable element.
175         NodeImpl *child = node->firstChild();
176         while (child) {
177             NodeImpl *remove = child;
178             child = child->nextSibling();
179             removeFullySelectedNode(remove);
180         }
181         
182         // make sure empty cell has some height
183         updateLayout();
184         RenderObject *r = node->renderer();
185         if (r && r->isTableCell() && r->contentHeight() <= 0)
186             insertBlockPlaceholder(Position(node,0));
187         return;
188     }
189     
190     removeNode(node);
191 }
192
193 void CompositeEditCommand::removeChildrenInRange(NodeImpl *node, int from, int to)
194 {
195     NodeImpl *nodeToRemove = node->childNode(from);
196     for (int i = from; i < to; i++) {
197         ASSERT(nodeToRemove);
198         NodeImpl *next = nodeToRemove->nextSibling();
199         removeNode(nodeToRemove);
200         nodeToRemove = next;
201     }
202 }
203
204 void CompositeEditCommand::removeNode(NodeImpl *removeChild)
205 {
206     EditCommandPtr cmd(new RemoveNodeCommand(document(), removeChild));
207     applyCommandToComposite(cmd);
208 }
209
210 void CompositeEditCommand::removeNodePreservingChildren(NodeImpl *removeChild)
211 {
212     EditCommandPtr cmd(new RemoveNodePreservingChildrenCommand(document(), removeChild));
213     applyCommandToComposite(cmd);
214 }
215
216 void CompositeEditCommand::splitTextNode(TextImpl *text, int offset)
217 {
218     EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, offset));
219     applyCommandToComposite(cmd);
220 }
221
222 void CompositeEditCommand::splitElement(ElementImpl *element, NodeImpl *atChild)
223 {
224     EditCommandPtr cmd(new SplitElementCommand(document(), element, atChild));
225     applyCommandToComposite(cmd);
226 }
227
228 void CompositeEditCommand::mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second)
229 {
230     EditCommandPtr cmd(new MergeIdenticalElementsCommand(document(), first, second));
231     applyCommandToComposite(cmd);
232 }
233
234 void CompositeEditCommand::wrapContentsInDummySpan(DOM::ElementImpl *element)
235 {
236     EditCommandPtr cmd(new WrapContentsInDummySpanCommand(document(), element));
237     applyCommandToComposite(cmd);
238 }
239
240 void CompositeEditCommand::splitTextNodeContainingElement(DOM::TextImpl *text, int offset)
241 {
242     EditCommandPtr cmd(new SplitTextNodeContainingElementCommand(document(), text, offset));
243     applyCommandToComposite(cmd);
244 }
245
246 void CompositeEditCommand::joinTextNodes(TextImpl *text1, TextImpl *text2)
247 {
248     EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2));
249     applyCommandToComposite(cmd);
250 }
251
252 void CompositeEditCommand::inputText(const DOMString &text, bool selectInsertedText)
253 {
254     InsertTextCommand *impl = new InsertTextCommand(document());
255     EditCommandPtr cmd(impl);
256     applyCommandToComposite(cmd);
257     impl->input(text, selectInsertedText);
258 }
259
260 void CompositeEditCommand::insertTextIntoNode(TextImpl *node, int offset, const DOMString &text)
261 {
262     EditCommandPtr cmd(new InsertIntoTextNodeCommand(document(), node, offset, text));
263     applyCommandToComposite(cmd);
264 }
265
266 void CompositeEditCommand::deleteTextFromNode(TextImpl *node, int offset, int count)
267 {
268     EditCommandPtr cmd(new DeleteFromTextNodeCommand(document(), node, offset, count));
269     applyCommandToComposite(cmd);
270 }
271
272 void CompositeEditCommand::replaceTextInNode(TextImpl *node, int offset, int count, const DOMString &replacementText)
273 {
274     EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count));
275     applyCommandToComposite(deleteCommand);
276     EditCommandPtr insertCommand(new InsertIntoTextNodeCommand(document(), node, offset, replacementText));
277     applyCommandToComposite(insertCommand);
278 }
279
280 Position CompositeEditCommand::positionOutsideTabSpan(const Position& pos)
281 {
282     if (!isTabSpanTextNode(pos.node()))
283         return pos;
284     
285     NodeImpl *tabSpan = tabSpanNode(pos.node());
286     
287     if (pos.offset() <= pos.node()->caretMinOffset())
288         return positionBeforeNode(tabSpan);
289         
290     if (pos.offset() >= pos.node()->caretMaxOffset())
291         return positionAfterNode(tabSpan);
292
293     splitTextNodeContainingElement(static_cast<TextImpl *>(pos.node()), pos.offset());
294     return positionBeforeNode(tabSpan);
295 }
296
297 void CompositeEditCommand::insertNodeAtTabSpanPosition(NodeImpl *node, const Position& pos)
298 {
299     // insert node before, after, or at split of tab span
300     Position insertPos = positionOutsideTabSpan(pos);
301     insertNodeAt(node, insertPos.node(), insertPos.offset());
302 }
303
304 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete)
305 {
306     if (endingSelection().isRange()) {
307         EditCommandPtr cmd(new DeleteSelectionCommand(document(), smartDelete, mergeBlocksAfterDelete));
308         applyCommandToComposite(cmd);
309     }
310 }
311
312 void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
313 {
314     if (selection.isRange()) {
315         EditCommandPtr cmd(new DeleteSelectionCommand(document(), selection, smartDelete, mergeBlocksAfterDelete));
316         applyCommandToComposite(cmd);
317     }
318 }
319
320 void CompositeEditCommand::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
321 {
322     EditCommandPtr cmd(new RemoveCSSPropertyCommand(document(), decl, property));
323     applyCommandToComposite(cmd);
324 }
325
326 void CompositeEditCommand::removeNodeAttribute(ElementImpl *element, const QualifiedName& attribute)
327 {
328     DOMString value = element->getAttribute(attribute);
329     if (value.isEmpty())
330         return;
331     EditCommandPtr cmd(new RemoveNodeAttributeCommand(document(), element, attribute));
332     applyCommandToComposite(cmd);
333 }
334
335 void CompositeEditCommand::setNodeAttribute(ElementImpl *element, const QualifiedName& attribute, const DOMString &value)
336 {
337     EditCommandPtr cmd(new SetNodeAttributeCommand(document(), element, attribute, value));
338     applyCommandToComposite(cmd);
339 }
340
341 void CompositeEditCommand::rebalanceWhitespaceAt(const Position &position)
342 {
343     EditCommandPtr cmd(new RebalanceWhitespaceCommand(document(), position));
344     applyCommandToComposite(cmd);    
345 }
346
347 void CompositeEditCommand::rebalanceWhitespace()
348 {
349     Selection selection = endingSelection();
350     if (selection.isCaretOrRange()) {
351         EditCommandPtr startCmd(new RebalanceWhitespaceCommand(document(), endingSelection().start()));
352         applyCommandToComposite(startCmd);
353         if (selection.isRange()) {
354             EditCommandPtr endCmd(new RebalanceWhitespaceCommand(document(), endingSelection().end()));
355             applyCommandToComposite(endCmd);
356         }
357     }
358 }
359
360 void CompositeEditCommand::deleteInsignificantText(TextImpl *textNode, int start, int end)
361 {
362     if (!textNode || !textNode->renderer() || start >= end)
363         return;
364
365     RenderText *textRenderer = static_cast<RenderText *>(textNode->renderer());
366     InlineTextBox *box = textRenderer->firstTextBox();
367     if (!box) {
368         // whole text node is empty
369         removeNode(textNode);
370         return;    
371     }
372     
373     int length = textNode->length();
374     if (start >= length || end > length)
375         return;
376
377     int removed = 0;
378     InlineTextBox *prevBox = 0;
379     RefPtr<DOMStringImpl> str;
380
381     // This loop structure works to process all gaps preceding a box,
382     // and also will look at the gap after the last box.
383     while (prevBox || box) {
384         int gapStart = prevBox ? prevBox->m_start + prevBox->m_len : 0;
385         if (end < gapStart)
386             // No more chance for any intersections
387             break;
388
389         int gapEnd = box ? box->m_start : length;
390         bool indicesIntersect = start <= gapEnd && end >= gapStart;
391         int gapLen = gapEnd - gapStart;
392         if (indicesIntersect && gapLen > 0) {
393             gapStart = kMax(gapStart, start);
394             gapEnd = kMin(gapEnd, end);
395             if (!str)
396                 str = textNode->string()->substring(start, end - start);
397             // remove text in the gap
398             str->remove(gapStart - start - removed, gapLen);
399             removed += gapLen;
400         }
401         
402         prevBox = box;
403         if (box)
404             box = box->nextTextBox();
405     }
406
407     if (str) {
408         // Replace the text between start and end with our pruned version.
409         if (str->length() > 0) {
410             replaceTextInNode(textNode, start, end - start, str.get());
411         } else {
412             // Assert that we are not going to delete all of the text in the node.
413             // If we were, that should have been done above with the call to 
414             // removeNode and return.
415             ASSERT(start > 0 || (unsigned)end - start < textNode->length());
416             deleteTextFromNode(textNode, start, end - start);
417         }
418     }
419 }
420
421 void CompositeEditCommand::deleteInsignificantText(const Position &start, const Position &end)
422 {
423     if (start.isNull() || end.isNull())
424         return;
425
426     if (RangeImpl::compareBoundaryPoints(start, end) >= 0)
427         return;
428
429     NodeImpl *node = start.node();
430     while (node) {
431         NodeImpl *next = node->traverseNextNode();
432     
433         if (node->isTextNode()) {
434             TextImpl *textNode = static_cast<TextImpl *>(node);
435             bool isStartNode = node == start.node();
436             bool isEndNode = node == end.node();
437             int startOffset = isStartNode ? start.offset() : 0;
438             int endOffset = isEndNode ? end.offset() : textNode->length();
439             deleteInsignificantText(textNode, startOffset, endOffset);
440         }
441             
442         if (node == end.node())
443             break;
444         node = next;
445     }
446 }
447
448 void CompositeEditCommand::deleteInsignificantTextDownstream(const DOM::Position &pos)
449 {
450     Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream();
451     deleteInsignificantText(pos, end);
452 }
453
454 NodeImpl *CompositeEditCommand::appendBlockPlaceholder(NodeImpl *node)
455 {
456     if (!node)
457         return NULL;
458     
459     // Should assert isBlockFlow || isInlineFlow when deletion improves.  See 4244964.
460     ASSERT(node->renderer());
461
462     RefPtr<NodeImpl> placeholder = createBlockPlaceholderElement(document());
463     appendNode(placeholder.get(), node);
464     return placeholder.get();
465 }
466
467 NodeImpl *CompositeEditCommand::insertBlockPlaceholder(const Position &pos)
468 {
469     if (pos.isNull())
470         return NULL;
471
472     // Should assert isBlockFlow || isInlineFlow when deletion improves.  See 4244964.
473     ASSERT(pos.node()->renderer());
474
475     RefPtr<NodeImpl> placeholder = createBlockPlaceholderElement(document());
476     insertNodeAt(placeholder.get(), pos.node(), pos.offset());
477     return placeholder.get();
478 }
479
480 NodeImpl *CompositeEditCommand::addBlockPlaceholderIfNeeded(NodeImpl *node)
481 {
482     if (!node)
483         return false;
484
485     updateLayout();
486
487     RenderObject *renderer = node->renderer();
488     if (!renderer || !renderer->isBlockFlow())
489         return false;
490     
491     // append the placeholder to make sure it follows
492     // any unrendered blocks
493     if (renderer->height() == 0 || (renderer->isListItem() && renderer->isEmpty()))
494         return appendBlockPlaceholder(node);
495
496     return NULL;
497 }
498
499 bool CompositeEditCommand::removeBlockPlaceholder(NodeImpl *node)
500 {
501     NodeImpl *placeholder = findBlockPlaceholder(node);
502     if (placeholder) {
503         removeNode(placeholder);
504         return true;
505     }
506     return false;
507 }
508
509 NodeImpl *CompositeEditCommand::findBlockPlaceholder(NodeImpl *node)
510 {
511     if (!node)
512         return 0;
513
514     updateLayout();
515
516     RenderObject *renderer = node->renderer();
517     if (!renderer || !renderer->isBlockFlow())
518         return 0;
519
520     for (NodeImpl *checkMe = node; checkMe; checkMe = checkMe->traverseNextNode(node)) {
521         if (checkMe->isElementNode()) {
522             ElementImpl *element = static_cast<ElementImpl *>(checkMe);
523             if (element->enclosingBlockFlowElement() == node && 
524                 element->getAttribute(classAttr) == blockPlaceholderClassString()) {
525                 return element;
526             }
527         }
528     }
529     
530     return 0;
531 }
532
533 void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position &pos)
534 {
535     if (pos.isNull())
536         return;
537     
538     updateLayout();
539     
540     VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
541     VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
542     VisiblePosition visibleParagraphEnd = endOfParagraph(visiblePos);
543     VisiblePosition next = visibleParagraphEnd.next();
544     VisiblePosition visibleEnd = next.isNotNull() ? next : visibleParagraphEnd;
545     
546     Position paragraphStart = visibleParagraphStart.deepEquivalent().upstream();
547     Position end = visibleEnd.deepEquivalent().upstream();
548     
549     // Perform some checks to see if we need to perform work in this function.
550     if (paragraphStart.node()->isBlockFlow()) {
551         if (end.node()->isBlockFlow()) {
552             if (!end.node()->isAncestor(paragraphStart.node())) {
553                 // If the paragraph end is a descendant of paragraph start, then we need to run
554                 // the rest of this function. If not, we can bail here.
555                 return;
556             }
557         }
558         else if (end.node()->enclosingBlockFlowElement() != paragraphStart.node()) {
559             // The paragraph end is in another block that is an ancestor of the paragraph start.
560             // We can bail as we have a full block to work with.
561             ASSERT(paragraphStart.node()->isAncestor(end.node()->enclosingBlockFlowElement()));
562             return;
563         }
564         else if (isEndOfDocument(visibleEnd)) {
565             // At the end of the document. We can bail here as well.
566             return;
567         }
568     }
569
570     RefPtr<NodeImpl> newBlock = createDefaultParagraphElement(document());
571
572     NodeImpl *moveNode = paragraphStart.node();
573     if (paragraphStart.offset() >= paragraphStart.node()->caretMaxOffset())
574         moveNode = moveNode->traverseNextNode();
575     NodeImpl *endNode = end.node();
576     
577     insertNodeAt(newBlock.get(), paragraphStart.node(), paragraphStart.offset());
578
579     while (moveNode && !moveNode->isBlockFlow()) {
580         NodeImpl *next = moveNode->traverseNextSibling();
581         removeNode(moveNode);
582         appendNode(moveNode, newBlock.get());
583         if (moveNode == endNode)
584             break;
585         moveNode = next;
586     }
587 }
588
589 PassRefPtr<ElementImpl> createBlockPlaceholderElement(DocumentImpl* document)
590 {
591     ExceptionCode ec = 0;
592     RefPtr<ElementImpl> breakNode = document->createElementNS(xhtmlNamespaceURI, "br", ec);
593     ASSERT(ec == 0);
594     breakNode->setAttribute(classAttr, blockPlaceholderClassString());
595     return breakNode.release();
596 }
597
598 static const DOMString &blockPlaceholderClassString()
599 {
600     static DOMString blockPlaceholderClassString = "khtml-block-placeholder";
601     return blockPlaceholderClassString;
602 }
603
604 } // namespace khtml