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