2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #include "composite_edit_command.h"
28 #include "append_node_command.h"
29 #include "htmlediting.h"
30 #include "visible_units.h"
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"
39 #include "KWQAssertions.h"
41 #define ASSERT(assertion) assert(assertion)
44 using DOM::CSSStyleDeclarationImpl;
45 using DOM::DocumentImpl;
47 using DOM::DOMStringImpl;
48 using DOM::ElementImpl;
56 static const DOMString &blockPlaceholderClassString();
58 //------------------------------------------------------------------------------------------
59 // CompositeEditCommand
61 CompositeEditCommand::CompositeEditCommand(DocumentImpl *document)
62 : EditCommand(document)
66 void CompositeEditCommand::doUnapply()
68 if (m_cmds.count() == 0) {
72 for (int i = m_cmds.count() - 1; i >= 0; --i)
78 void CompositeEditCommand::doReapply()
80 if (m_cmds.count() == 0) {
84 for (QValueList<EditCommandPtr>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
91 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
93 void CompositeEditCommand::applyCommandToComposite(EditCommandPtr &cmd)
95 cmd.setStartingSelection(endingSelection());
96 cmd.setEndingSelection(endingSelection());
102 void CompositeEditCommand::applyStyle(CSSStyleDeclarationImpl *style, EditAction editingAction)
104 EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction));
105 applyCommandToComposite(cmd);
108 void CompositeEditCommand::insertParagraphSeparator()
110 EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
111 applyCommandToComposite(cmd);
114 void CompositeEditCommand::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
116 ASSERT(refChild->id() != ID_BODY);
117 EditCommandPtr cmd(new InsertNodeBeforeCommand(document(), insertChild, refChild));
118 applyCommandToComposite(cmd);
121 void CompositeEditCommand::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
123 ASSERT(refChild->id() != ID_BODY);
124 if (refChild->parentNode()->lastChild() == refChild) {
125 appendNode(insertChild, refChild->parentNode());
128 ASSERT(refChild->nextSibling());
129 insertNodeBefore(insertChild, refChild->nextSibling());
133 void CompositeEditCommand::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
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();
140 insertNodeBefore(insertChild, child);
142 appendNode(insertChild, refChild);
144 else if (refChild->caretMinOffset() >= offset) {
145 insertNodeBefore(insertChild, refChild);
147 else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
148 splitTextNode(static_cast<TextImpl *>(refChild), offset);
149 insertNodeBefore(insertChild, refChild);
152 insertNodeAfter(insertChild, refChild);
156 void CompositeEditCommand::appendNode(NodeImpl *appendChild, NodeImpl *parent)
158 EditCommandPtr cmd(new AppendNodeCommand(document(), appendChild, parent));
159 applyCommandToComposite(cmd);
162 void CompositeEditCommand::removeFullySelectedNode(NodeImpl *node)
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();
169 NodeImpl *remove = child;
170 child = child->nextSibling();
171 removeFullySelectedNode(remove);
179 void CompositeEditCommand::removeChildrenInRange(NodeImpl *node, int from, int to)
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);
190 void CompositeEditCommand::removeNode(NodeImpl *removeChild)
192 EditCommandPtr cmd(new RemoveNodeCommand(document(), removeChild));
193 applyCommandToComposite(cmd);
196 void CompositeEditCommand::removeNodePreservingChildren(NodeImpl *removeChild)
198 EditCommandPtr cmd(new RemoveNodePreservingChildrenCommand(document(), removeChild));
199 applyCommandToComposite(cmd);
202 void CompositeEditCommand::splitTextNode(TextImpl *text, long offset)
204 EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, offset));
205 applyCommandToComposite(cmd);
208 void CompositeEditCommand::splitElement(ElementImpl *element, NodeImpl *atChild)
210 EditCommandPtr cmd(new SplitElementCommand(document(), element, atChild));
211 applyCommandToComposite(cmd);
214 void CompositeEditCommand::mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second)
216 EditCommandPtr cmd(new MergeIdenticalElementsCommand(document(), first, second));
217 applyCommandToComposite(cmd);
220 void CompositeEditCommand::wrapContentsInDummySpan(DOM::ElementImpl *element)
222 EditCommandPtr cmd(new WrapContentsInDummySpanCommand(document(), element));
223 applyCommandToComposite(cmd);
226 void CompositeEditCommand::splitTextNodeContainingElement(DOM::TextImpl *text, long offset)
228 EditCommandPtr cmd(new SplitTextNodeContainingElementCommand(document(), text, offset));
229 applyCommandToComposite(cmd);
232 void CompositeEditCommand::joinTextNodes(TextImpl *text1, TextImpl *text2)
234 EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2));
235 applyCommandToComposite(cmd);
238 void CompositeEditCommand::inputText(const DOMString &text, bool selectInsertedText)
240 InsertTextCommand *impl = new InsertTextCommand(document());
241 EditCommandPtr cmd(impl);
242 applyCommandToComposite(cmd);
243 impl->input(text, selectInsertedText);
246 void CompositeEditCommand::insertTextIntoNode(TextImpl *node, long offset, const DOMString &text)
248 EditCommandPtr cmd(new InsertIntoTextNode(document(), node, offset, text));
249 applyCommandToComposite(cmd);
252 void CompositeEditCommand::deleteTextFromNode(TextImpl *node, long offset, long count)
254 EditCommandPtr cmd(new DeleteFromTextNodeCommand(document(), node, offset, count));
255 applyCommandToComposite(cmd);
258 void CompositeEditCommand::replaceTextInNode(TextImpl *node, long offset, long count, const DOMString &replacementText)
260 EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count));
261 applyCommandToComposite(deleteCommand);
262 EditCommandPtr insertCommand(new InsertIntoTextNode(document(), node, offset, replacementText));
263 applyCommandToComposite(insertCommand);
266 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete)
268 if (endingSelection().isRange()) {
269 EditCommandPtr cmd(new DeleteSelectionCommand(document(), smartDelete, mergeBlocksAfterDelete));
270 applyCommandToComposite(cmd);
274 void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
276 if (selection.isRange()) {
277 EditCommandPtr cmd(new DeleteSelectionCommand(document(), selection, smartDelete, mergeBlocksAfterDelete));
278 applyCommandToComposite(cmd);
282 void CompositeEditCommand::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
284 EditCommandPtr cmd(new RemoveCSSPropertyCommand(document(), decl, property));
285 applyCommandToComposite(cmd);
288 void CompositeEditCommand::removeNodeAttribute(ElementImpl *element, int attribute)
290 DOMString value = element->getAttribute(attribute);
293 EditCommandPtr cmd(new RemoveNodeAttributeCommand(document(), element, attribute));
294 applyCommandToComposite(cmd);
297 void CompositeEditCommand::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
299 EditCommandPtr cmd(new SetNodeAttributeCommand(document(), element, attribute, value));
300 applyCommandToComposite(cmd);
303 void CompositeEditCommand::rebalanceWhitespace()
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);
316 void CompositeEditCommand::deleteInsignificantText(TextImpl *textNode, int start, int end)
318 if (!textNode || !textNode->renderer() || start >= end)
321 RenderText *textRenderer = static_cast<RenderText *>(textNode->renderer());
322 InlineTextBox *box = textRenderer->firstTextBox();
324 // whole text node is empty
325 removeNode(textNode);
329 long length = textNode->length();
330 if (start >= length || end > length)
334 InlineTextBox *prevBox = 0;
335 DOMStringImpl *str = 0;
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;
342 // No more chance for any intersections
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);
352 str = textNode->string()->substring(start, end - start);
355 // remove text in the gap
356 str->remove(gapStart - start - removed, gapLen);
362 box = box->nextTextBox();
366 // Replace the text between start and end with our pruned version.
368 replaceTextInNode(textNode, start, end - start, str);
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);
381 void CompositeEditCommand::deleteInsignificantText(const Position &start, const Position &end)
383 if (start.isNull() || end.isNull())
386 if (RangeImpl::compareBoundaryPoints(start, end) >= 0)
389 NodeImpl *node = start.node();
391 NodeImpl *next = node->traverseNextNode();
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);
402 if (node == end.node())
408 void CompositeEditCommand::deleteInsignificantTextDownstream(const DOM::Position &pos)
410 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream();
411 deleteInsignificantText(pos, end);
414 NodeImpl *CompositeEditCommand::appendBlockPlaceholder(NodeImpl *node)
419 ASSERT(node->renderer() && node->renderer()->isBlockFlow());
421 NodeImpl *placeholder = createBlockPlaceholderElement(document());
422 appendNode(placeholder, node);
426 NodeImpl *CompositeEditCommand::insertBlockPlaceholder(const Position &pos)
431 ASSERT(pos.node()->renderer() && pos.node()->renderer()->isBlockFlow());
433 NodeImpl *placeholder = createBlockPlaceholderElement(document());
434 insertNodeAt(placeholder, pos.node(), pos.offset());
438 NodeImpl *CompositeEditCommand::addBlockPlaceholderIfNeeded(NodeImpl *node)
443 document()->updateLayout();
445 RenderObject *renderer = node->renderer();
446 if (!renderer || !renderer->isBlockFlow())
449 // append the placeholder to make sure it follows
450 // any unrendered blocks
451 if (renderer->height() == 0) {
452 return appendBlockPlaceholder(node);
458 bool CompositeEditCommand::removeBlockPlaceholder(NodeImpl *node)
460 NodeImpl *placeholder = findBlockPlaceholder(node);
462 removeNode(placeholder);
468 NodeImpl *CompositeEditCommand::findBlockPlaceholder(NodeImpl *node)
473 document()->updateLayout();
475 RenderObject *renderer = node->renderer();
476 if (!renderer || !renderer->isBlockFlow())
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()) {
492 void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position &pos)
497 document()->updateLayout();
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();
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.
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()));
520 else if (isEndOfDocument(visibleParagraphEnd)) {
521 // At the end of the document. We can bail here as well.
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());
537 newBlock = startBlock->cloneNode(false);
539 NodeImpl *moveNode = paragraphStart.node();
540 if (paragraphStart.offset() >= paragraphStart.node()->caretMaxOffset())
541 moveNode = moveNode->traverseNextNode();
542 NodeImpl *endNode = paragraphEnd.node();
544 if (paragraphStart.node()->id() == ID_BODY) {
545 insertNodeAt(newBlock, paragraphStart.node(), 0);
547 else if (paragraphStart.node()->id() == ID_BR) {
548 insertNodeAfter(newBlock, paragraphStart.node());
551 insertNodeBefore(newBlock, paragraphStart.upstream().node());
554 while (moveNode && !moveNode->isBlockFlow()) {
555 NodeImpl *next = moveNode->traverseNextSibling();
556 removeNode(moveNode);
557 appendNode(moveNode, newBlock);
558 if (moveNode == endNode)
564 ElementImpl *createBlockPlaceholderElement(DocumentImpl *document)
566 int exceptionCode = 0;
567 ElementImpl *breakNode = document->createHTMLElement("br", exceptionCode);
568 ASSERT(exceptionCode == 0);
569 breakNode->setAttribute(ATTR_CLASS, blockPlaceholderClassString());
573 static const DOMString &blockPlaceholderClassString()
575 static DOMString blockPlaceholderClassString = "khtml-block-placeholder";
576 return blockPlaceholderClassString;