File indexing completed on 2024-04-28 15:23:23

0001 /*
0002  * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
0003  *
0004  * Redistribution and use in source and binary forms, with or without
0005  * modification, are permitted provided that the following conditions
0006  * are met:
0007  * 1. Redistributions of source code must retain the above copyright
0008  *    notice, this list of conditions and the following disclaimer.
0009  * 2. Redistributions in binary form must reproduce the above copyright
0010  *    notice, this list of conditions and the following disclaimer in the
0011  *    documentation and/or other materials provided with the distribution.
0012  *
0013  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
0014  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0015  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
0016  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
0017  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0018  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0019  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0020  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
0021  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0022  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
0023  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0024  */
0025 
0026 #include "htmlediting_impl.h"
0027 #include "editor.h"
0028 
0029 #include "css/cssproperties.h"
0030 #include "css/css_valueimpl.h"
0031 #include "dom/css_value.h"
0032 #include "html/html_elementimpl.h"
0033 #include "html/html_imageimpl.h"
0034 #include "rendering/render_object.h"
0035 #include "rendering/render_style.h"
0036 #include "rendering/render_text.h"
0037 #include "xml/dom_docimpl.h"
0038 #include "xml/dom_elementimpl.h"
0039 #include "xml/dom_positioniterator.h"
0040 #include "xml/dom_stringimpl.h"
0041 #include "xml/dom_textimpl.h"
0042 #include "xml/dom2_rangeimpl.h"
0043 #include "xml/dom2_viewsimpl.h"
0044 
0045 #include "khtml_part.h"
0046 #include "khtmlview.h"
0047 
0048 #include <QScopedPointer>
0049 #include <limits.h>
0050 
0051 using DOM::AttrImpl;
0052 using DOM::CSSPrimitiveValue;
0053 using DOM::CSSPrimitiveValueImpl;
0054 using DOM::CSSProperty;
0055 using DOM::CSSStyleDeclarationImpl;
0056 using DOM::CSSValueImpl;
0057 using DOM::DocumentFragmentImpl;
0058 using DOM::DocumentImpl;
0059 using DOM::DOMString;
0060 using DOM::DOMStringImpl;
0061 using DOM::EditingTextImpl;
0062 using DOM::PositionIterator;
0063 using DOM::ElementImpl;
0064 using DOM::HTMLElementImpl;
0065 using DOM::HTMLImageElementImpl;
0066 using DOM::NamedAttrMapImpl;
0067 using DOM::Node;
0068 using DOM::NodeImpl;
0069 using DOM::NodeListImpl;
0070 using DOM::Position;
0071 using DOM::Range;
0072 using DOM::RangeImpl;
0073 using DOM::Selection;
0074 using DOM::TextImpl;
0075 using DOM::TreeWalkerImpl;
0076 using DOM::Editor;
0077 
0078 #define DEBUG_COMMANDS 0
0079 
0080 namespace khtml
0081 {
0082 
0083 static inline bool isNBSP(const QChar &c)
0084 {
0085     return c == QChar(0xa0);
0086 }
0087 
0088 static inline bool isWS(const QChar &c)
0089 {
0090     return c.isSpace() && c != QChar(0xa0);
0091 }
0092 
0093 static inline bool isWS(const DOMString &text)
0094 {
0095     if (text.length() != 1) {
0096         return false;
0097     }
0098 
0099     return isWS(text[0]);
0100 }
0101 
0102 static inline bool isWS(const Position &pos)
0103 {
0104     if (!pos.node()) {
0105         return false;
0106     }
0107 
0108     if (!pos.node()->isTextNode()) {
0109         return false;
0110     }
0111 
0112     const DOMString &string = static_cast<TextImpl *>(pos.node())->data();
0113     return isWS(string[pos.offset()]);
0114 }
0115 
0116 static bool shouldPruneNode(NodeImpl *node)
0117 {
0118     if (!node) {
0119         return false;
0120     }
0121 
0122     RenderObject *renderer = node->renderer();
0123     if (!renderer) {
0124         return true;
0125     }
0126 
0127     if (node->hasChildNodes()) {
0128         return false;
0129     }
0130 
0131     if (node->rootEditableElement() == node) {
0132         return false;
0133     }
0134 
0135     if (renderer->isBR() || renderer->isReplaced()) {
0136         return false;
0137     }
0138 
0139     if (node->isTextNode()) {
0140         TextImpl *text = static_cast<TextImpl *>(node);
0141         if (text->length() == 0) {
0142             return true;
0143         }
0144         return false;
0145     }
0146 
0147     if (!node->isHTMLElement()/* && !node->isXMLElementNode()*/) {
0148         return false;
0149     }
0150 
0151     if (node->id() == ID_BODY) {
0152         return false;
0153     }
0154 
0155     if (!node->isContentEditable()) {
0156         return false;
0157     }
0158 
0159     return true;
0160 }
0161 
0162 static Position leadingWhitespacePosition(const Position &pos)
0163 {
0164     assert(pos.notEmpty());
0165 
0166     Selection selection(pos);
0167     Position prev = pos.previousCharacterPosition();
0168     if (prev != pos && prev.node()->inSameContainingBlockFlowElement(pos.node()) && prev.node()->isTextNode()) {
0169         DOMString string = static_cast<TextImpl *>(prev.node())->data();
0170         if (isWS(string[prev.offset()])) {
0171             return prev;
0172         }
0173     }
0174 
0175     return Position();
0176 }
0177 
0178 static Position trailingWhitespacePosition(const Position &pos)
0179 {
0180     assert(pos.notEmpty());
0181 
0182     if (pos.node()->isTextNode()) {
0183         TextImpl *textNode = static_cast<TextImpl *>(pos.node());
0184         if (pos.offset() >= (long)textNode->length()) {
0185             Position next = pos.nextCharacterPosition();
0186             if (next != pos && next.node()->inSameContainingBlockFlowElement(pos.node()) && next.node()->isTextNode()) {
0187                 DOMString string = static_cast<TextImpl *>(next.node())->data();
0188                 if (isWS(string[0])) {
0189                     return next;
0190                 }
0191             }
0192         } else {
0193             DOMString string = static_cast<TextImpl *>(pos.node())->data();
0194             if (isWS(string[pos.offset()])) {
0195                 return pos;
0196             }
0197         }
0198     }
0199 
0200     return Position();
0201 }
0202 
0203 static bool textNodesAreJoinable(TextImpl *text1, TextImpl *text2)
0204 {
0205     assert(text1);
0206     assert(text2);
0207 
0208     return (text1->nextSibling() == text2);
0209 }
0210 
0211 static DOMString &nonBreakingSpaceString()
0212 {
0213     static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
0214     return nonBreakingSpaceString;
0215 }
0216 
0217 static DOMString &styleSpanClassString()
0218 {
0219     static DOMString styleSpanClassString = "khtml-style-span";
0220     return styleSpanClassString;
0221 }
0222 
0223 //------------------------------------------------------------------------------------------
0224 // EditCommandImpl
0225 
0226 EditCommandImpl::EditCommandImpl(DocumentImpl *document)
0227     : SharedCommandImpl(), m_document(document), m_state(NotApplied), m_parent(nullptr)
0228 {
0229     assert(m_document);
0230     assert(m_document->part());
0231     m_document->ref();
0232     m_startingSelection = m_document->part()->caret();
0233     m_endingSelection = m_startingSelection;
0234 }
0235 
0236 EditCommandImpl::~EditCommandImpl()
0237 {
0238     m_document->deref();
0239 }
0240 
0241 void EditCommandImpl::apply()
0242 {
0243     assert(m_document);
0244     assert(m_document->part());
0245     assert(state() == NotApplied);
0246 
0247     doApply();
0248 
0249     m_state = Applied;
0250 
0251     if (!isCompositeStep()) {
0252         m_document->part()->editor()->appliedEditing(this);
0253     }
0254 }
0255 
0256 void EditCommandImpl::unapply()
0257 {
0258     assert(m_document);
0259     assert(m_document->part());
0260     assert(state() == Applied);
0261 
0262     doUnapply();
0263 
0264     m_state = NotApplied;
0265 
0266     if (!isCompositeStep()) {
0267         m_document->part()->editor()->unappliedEditing(this);
0268     }
0269 }
0270 
0271 void EditCommandImpl::reapply()
0272 {
0273     assert(m_document);
0274     assert(m_document->part());
0275     assert(state() == NotApplied);
0276 
0277     doReapply();
0278 
0279     m_state = Applied;
0280 
0281     if (!isCompositeStep()) {
0282         m_document->part()->editor()->reappliedEditing(this);
0283     }
0284 }
0285 
0286 void EditCommandImpl::doReapply()
0287 {
0288     doApply();
0289 }
0290 
0291 void EditCommandImpl::setStartingSelection(const Selection &s)
0292 {
0293     m_startingSelection = s;
0294     EditCommandImpl *cmd = parent();
0295     while (cmd) {
0296         cmd->m_startingSelection = s;
0297         cmd = cmd->parent();
0298     }
0299 }
0300 
0301 void EditCommandImpl::setEndingSelection(const Selection &s)
0302 {
0303     m_endingSelection = s;
0304     EditCommandImpl *cmd = parent();
0305     while (cmd) {
0306         cmd->m_endingSelection = s;
0307         cmd = cmd->parent();
0308     }
0309 }
0310 
0311 EditCommandImpl *EditCommandImpl::parent() const
0312 {
0313     return m_parent.get();
0314 }
0315 
0316 void EditCommandImpl::setParent(EditCommandImpl *cmd)
0317 {
0318     m_parent = cmd;
0319 }
0320 
0321 //------------------------------------------------------------------------------------------
0322 // CompositeEditCommandImpl
0323 
0324 CompositeEditCommandImpl::CompositeEditCommandImpl(DocumentImpl *document)
0325     : EditCommandImpl(document)
0326 {
0327 }
0328 
0329 CompositeEditCommandImpl::~CompositeEditCommandImpl()
0330 {
0331 }
0332 
0333 void CompositeEditCommandImpl::doUnapply()
0334 {
0335     if (m_cmds.count() == 0) {
0336         return;
0337     }
0338 
0339     for (int i = m_cmds.count() - 1; i >= 0; --i) {
0340         m_cmds[i]->unapply();
0341     }
0342 
0343     setState(NotApplied);
0344 }
0345 
0346 void CompositeEditCommandImpl::doReapply()
0347 {
0348     if (m_cmds.count() == 0) {
0349         return;
0350     }
0351     QMutableListIterator<RefPtr<EditCommandImpl> > it(m_cmds);
0352     while (it.hasNext()) {
0353         it.next()->reapply();
0354     }
0355 
0356     setState(Applied);
0357 }
0358 
0359 //
0360 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
0361 //
0362 void CompositeEditCommandImpl::applyCommandToComposite(PassRefPtr<EditCommandImpl> cmd)
0363 {
0364     cmd->setStartingSelection(endingSelection());//###?
0365     cmd->setEndingSelection(endingSelection());
0366     cmd->setParent(this);
0367     cmd->apply();
0368     m_cmds.append(cmd);
0369 }
0370 
0371 void CompositeEditCommandImpl::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
0372 {
0373     RefPtr<InsertNodeBeforeCommandImpl> cmd = new InsertNodeBeforeCommandImpl(document(), insertChild, refChild);
0374     applyCommandToComposite(cmd);
0375 }
0376 
0377 void CompositeEditCommandImpl::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
0378 {
0379     if (refChild->parentNode()->lastChild() == refChild) {
0380         appendNode(refChild->parentNode(), insertChild);
0381     } else {
0382         assert(refChild->nextSibling());
0383         insertNodeBefore(insertChild, refChild->nextSibling());
0384     }
0385 }
0386 
0387 void CompositeEditCommandImpl::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
0388 {
0389     if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
0390         NodeImpl *child = refChild->firstChild();
0391         for (long i = 0; child && i < offset; i++) {
0392             child = child->nextSibling();
0393         }
0394         if (child) {
0395             insertNodeBefore(insertChild, child);
0396         } else {
0397             appendNode(refChild, insertChild);
0398         }
0399     } else if (refChild->caretMinOffset() >= offset) {
0400         insertNodeBefore(insertChild, refChild);
0401     } else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
0402         splitTextNode(static_cast<TextImpl *>(refChild), offset);
0403         insertNodeBefore(insertChild, refChild);
0404     } else {
0405         insertNodeAfter(insertChild, refChild);
0406     }
0407 }
0408 
0409 void CompositeEditCommandImpl::appendNode(NodeImpl *parent, NodeImpl *appendChild)
0410 {
0411     RefPtr<AppendNodeCommandImpl> cmd = new AppendNodeCommandImpl(document(), parent, appendChild);
0412     applyCommandToComposite(cmd);
0413 }
0414 
0415 void CompositeEditCommandImpl::removeNode(NodeImpl *removeChild)
0416 {
0417     RefPtr<RemoveNodeCommandImpl> cmd = new RemoveNodeCommandImpl(document(), removeChild);
0418     applyCommandToComposite(cmd);
0419 }
0420 
0421 void CompositeEditCommandImpl::removeNodeAndPrune(NodeImpl *pruneNode, NodeImpl *stopNode)
0422 {
0423     RefPtr<RemoveNodeAndPruneCommandImpl> cmd = new RemoveNodeAndPruneCommandImpl(document(), pruneNode, stopNode);
0424     applyCommandToComposite(cmd);
0425 }
0426 
0427 void CompositeEditCommandImpl::removeNodePreservingChildren(NodeImpl *removeChild)
0428 {
0429     RefPtr<RemoveNodePreservingChildrenCommandImpl> cmd = new RemoveNodePreservingChildrenCommandImpl(document(), removeChild);
0430     applyCommandToComposite(cmd);
0431 }
0432 
0433 void CompositeEditCommandImpl::splitTextNode(TextImpl *text, long offset)
0434 {
0435     RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, offset);
0436     applyCommandToComposite(cmd);
0437 }
0438 
0439 void CompositeEditCommandImpl::joinTextNodes(TextImpl *text1, TextImpl *text2)
0440 {
0441     RefPtr<JoinTextNodesCommandImpl> cmd = new JoinTextNodesCommandImpl(document(), text1, text2);
0442     applyCommandToComposite(cmd);
0443 }
0444 
0445 void CompositeEditCommandImpl::inputText(const DOMString &text)
0446 {
0447     RefPtr<InputTextCommandImpl> cmd = new InputTextCommandImpl(document());
0448     applyCommandToComposite(cmd);
0449     cmd->input(text);
0450 }
0451 
0452 void CompositeEditCommandImpl::insertText(TextImpl *node, long offset, const DOMString &text)
0453 {
0454     RefPtr<InsertTextCommandImpl> cmd = new InsertTextCommandImpl(document(), node, offset, text);
0455     applyCommandToComposite(cmd);
0456 }
0457 
0458 void CompositeEditCommandImpl::deleteText(TextImpl *node, long offset, long count)
0459 {
0460     RefPtr<DeleteTextCommandImpl> cmd = new DeleteTextCommandImpl(document(), node, offset, count);
0461     applyCommandToComposite(cmd);
0462 }
0463 
0464 void CompositeEditCommandImpl::replaceText(TextImpl *node, long offset, long count, const DOMString &replacementText)
0465 {
0466     RefPtr<DeleteTextCommandImpl> deleteCommand = new DeleteTextCommandImpl(document(), node, offset, count);
0467     applyCommandToComposite(deleteCommand);
0468     RefPtr<InsertTextCommandImpl> insertCommand = new InsertTextCommandImpl(document(), node, offset, replacementText);
0469     applyCommandToComposite(insertCommand);
0470 }
0471 
0472 void CompositeEditCommandImpl::deleteSelection()
0473 {
0474     if (endingSelection().state() == Selection::RANGE) {
0475         RefPtr<DeleteSelectionCommandImpl> cmd = new DeleteSelectionCommandImpl(document());
0476         applyCommandToComposite(cmd);
0477     }
0478 }
0479 
0480 void CompositeEditCommandImpl::deleteSelection(const Selection &selection)
0481 {
0482     if (selection.state() == Selection::RANGE) {
0483         RefPtr<DeleteSelectionCommandImpl> cmd = new DeleteSelectionCommandImpl(document(), selection);
0484         applyCommandToComposite(cmd);
0485     }
0486 }
0487 
0488 void CompositeEditCommandImpl::deleteCollapsibleWhitespace()
0489 {
0490     RefPtr<DeleteCollapsibleWhitespaceCommandImpl> cmd = new DeleteCollapsibleWhitespaceCommandImpl(document());
0491     applyCommandToComposite(cmd);
0492 }
0493 
0494 void CompositeEditCommandImpl::deleteCollapsibleWhitespace(const Selection &selection)
0495 {
0496     RefPtr<DeleteCollapsibleWhitespaceCommandImpl> cmd = new DeleteCollapsibleWhitespaceCommandImpl(document(), selection);
0497     applyCommandToComposite(cmd);
0498 }
0499 
0500 void CompositeEditCommandImpl::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
0501 {
0502     RefPtr<RemoveCSSPropertyCommandImpl> cmd = new RemoveCSSPropertyCommandImpl(document(), decl, property);
0503     applyCommandToComposite(cmd);
0504 }
0505 
0506 void CompositeEditCommandImpl::removeNodeAttribute(ElementImpl *element, int attribute)
0507 {
0508     RefPtr<RemoveNodeAttributeCommandImpl> cmd = new RemoveNodeAttributeCommandImpl(document(), element, attribute);
0509     applyCommandToComposite(cmd);
0510 }
0511 
0512 void CompositeEditCommandImpl::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
0513 {
0514     RefPtr<SetNodeAttributeCommandImpl> cmd = new SetNodeAttributeCommandImpl(document(), element, attribute, value);
0515     applyCommandToComposite(cmd);
0516 }
0517 
0518 ElementImpl *CompositeEditCommandImpl::createTypingStyleElement() const
0519 {
0520     ElementImpl *styleElement = document()->createHTMLElement("SPAN");
0521 
0522     styleElement->setAttribute(ATTR_STYLE, document()->part()->editor()->typingStyle()->cssText().implementation());
0523 
0524     styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
0525 
0526     return styleElement;
0527 }
0528 
0529 //==========================================================================================
0530 // Concrete commands
0531 //------------------------------------------------------------------------------------------
0532 // AppendNodeCommandImpl
0533 
0534 AppendNodeCommandImpl::AppendNodeCommandImpl(DocumentImpl *document, NodeImpl *parentNode, NodeImpl *appendChild)
0535     : EditCommandImpl(document), m_parentNode(parentNode), m_appendChild(appendChild)
0536 {
0537     assert(m_parentNode);
0538     m_parentNode->ref();
0539 
0540     assert(m_appendChild);
0541     m_appendChild->ref();
0542 }
0543 
0544 AppendNodeCommandImpl::~AppendNodeCommandImpl()
0545 {
0546     if (m_parentNode) {
0547         m_parentNode->deref();
0548     }
0549     if (m_appendChild) {
0550         m_appendChild->deref();
0551     }
0552 }
0553 
0554 void AppendNodeCommandImpl::doApply()
0555 {
0556     assert(m_parentNode);
0557     assert(m_appendChild);
0558 
0559     int exceptionCode = 0;
0560     m_parentNode->appendChild(m_appendChild, exceptionCode);
0561     assert(exceptionCode == 0);
0562 }
0563 
0564 void AppendNodeCommandImpl::doUnapply()
0565 {
0566     assert(m_parentNode);
0567     assert(m_appendChild);
0568     assert(state() == Applied);
0569 
0570     int exceptionCode = 0;
0571     m_parentNode->removeChild(m_appendChild, exceptionCode);
0572     assert(exceptionCode == 0);
0573 }
0574 
0575 //------------------------------------------------------------------------------------------
0576 // ApplyStyleCommandImpl
0577 
0578 ApplyStyleCommandImpl::ApplyStyleCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *style)
0579     : CompositeEditCommandImpl(document), m_style(style)
0580 {
0581     assert(m_style);
0582     m_style->ref();
0583 }
0584 
0585 ApplyStyleCommandImpl::~ApplyStyleCommandImpl()
0586 {
0587     assert(m_style);
0588     m_style->deref();
0589 }
0590 
0591 static bool isBlockLevelStyle(const CSSStyleDeclarationImpl *style)
0592 {
0593     QListIterator<CSSProperty *> it(*(style->values()));
0594     while (it.hasNext()) {
0595         CSSProperty *property = it.next();
0596         switch (property->id()) {
0597         case CSS_PROP_TEXT_ALIGN:
0598             return true;
0599             /*case CSS_PROP_FONT_WEIGHT:
0600                 if (strcasecmp(property->value()->cssText(), "bold") == 0)
0601                     styleChange.applyBold = true;
0602                 else
0603                     styleChange.cssStyle += property->cssText();
0604                 break;
0605             case CSS_PROP_FONT_STYLE: {
0606                     DOMString cssText(property->value()->cssText());
0607                     if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0)
0608                         styleChange.applyItalic = true;
0609                     else
0610                         styleChange.cssStyle += property->cssText();
0611                 }
0612                 break;
0613             default:
0614                 styleChange.cssStyle += property->cssText();
0615                 break;*/
0616         }
0617     }
0618     return false;
0619 }
0620 
0621 static void applyStyleChangeOnTheNode(ElementImpl *element, CSSStyleDeclarationImpl *style)
0622 {
0623     QScopedPointer<CSSStyleDeclarationImpl> computedStyle(
0624             element->document()->defaultView()->getComputedStyle(element, nullptr));
0625     assert(!computedStyle.isNull());
0626 #ifdef DEBUG_COMMANDS
0627     qCDebug(KHTML_LOG) << "[change style]" << element;
0628 #endif
0629 
0630     QListIterator<CSSProperty *> it(*(style->values()));
0631     while (it.hasNext()) {
0632         CSSProperty *property = it.next();
0633         CSSValueImpl *computedValue = computedStyle->getPropertyCSSValue(property->id());
0634         DOMString newValue = property->value()->cssText();
0635 #ifdef DEBUG_COMMANDS
0636         qCDebug(KHTML_LOG) << "[new value]:" << property->cssText();
0637         qCDebug(KHTML_LOG) << "[computedValue]:" << computedValue->cssText();
0638 #endif
0639         if (strcasecmp(computedValue->cssText(), newValue)) {
0640             // we can do better and avoid parsing property
0641             element->getInlineStyleDecls()->setProperty(property->id(), newValue);
0642         }
0643     }
0644 }
0645 
0646 void ApplyStyleCommandImpl::doApply()
0647 {
0648     if (endingSelection().state() != Selection::RANGE) {
0649         return;
0650     }
0651 
0652     // adjust to the positions we want to use for applying style
0653     Position start(endingSelection().start().equivalentDownstreamPosition().equivalentRangeCompliantPosition());
0654     Position end(endingSelection().end().equivalentUpstreamPosition());
0655 #ifdef DEBUG_COMMANDS
0656     qCDebug(KHTML_LOG) << "[APPLY STYLE]" << start << end;
0657     printEnclosingBlockTree(start.node()->enclosingBlockFlowElement());
0658 #endif
0659 
0660     if (isBlockLevelStyle(m_style)) {
0661 #ifdef DEBUG_COMMANDS
0662         qCDebug(KHTML_LOG) << "[APPLY BLOCK LEVEL STYLE]";
0663 #endif
0664         ElementImpl *startBlock = start.node()->enclosingBlockFlowElement();
0665         ElementImpl *endBlock   = end.node()->enclosingBlockFlowElement();
0666 #ifdef DEBUG_COMMANDS
0667         qCDebug(KHTML_LOG) << startBlock << startBlock->nodeName();
0668 #endif
0669         if (startBlock == endBlock && startBlock == start.node()->rootEditableElement()) {
0670             ElementImpl *block = document()->createHTMLElement("DIV");
0671 #ifdef DEBUG_COMMANDS
0672             qCDebug(KHTML_LOG) << "[Create DIV with Style:]" << m_style->cssText();
0673 #endif
0674             block->setAttribute(ATTR_STYLE, m_style->cssText());
0675             for (NodeImpl *node = startBlock->firstChild(); node; node = startBlock->firstChild()) {
0676 #ifdef DEBUG_COMMANDS
0677                 qCDebug(KHTML_LOG) << "[reparent node]" << node << node->nodeName();
0678 #endif
0679                 removeNode(node);
0680                 appendNode(block, node);
0681             }
0682             appendNode(startBlock, block);
0683         } else if (startBlock == endBlock) {
0684             // StyleChange styleChange = computeStyleChange(Position(startBlock, 0), m_style);
0685             //qCDebug(KHTML_LOG) << "[Modify block with style change:]" << styleChange.cssStyle;
0686             applyStyleChangeOnTheNode(startBlock, m_style);
0687             // startBlock->setAttribute(ATTR_STYLE, styleChange.cssStyle);
0688         }
0689         return;
0690     }
0691 
0692     // remove style from the selection
0693     removeStyle(start, end);
0694     bool splitStart = splitTextAtStartIfNeeded(start, end);
0695     if (splitStart) {
0696         start = endingSelection().start();
0697         end = endingSelection().end();
0698     }
0699     splitTextAtEndIfNeeded(start, end);
0700     start = endingSelection().start();
0701     end = endingSelection().end();
0702 
0703 #ifdef DEBUG_COMMANDS
0704     qCDebug(KHTML_LOG) << "[start;end]" << start << end;
0705 #endif
0706     if (start.node() == end.node()) {
0707         // simple case...start and end are the same node
0708         applyStyleIfNeeded(start.node(), end.node());
0709     } else {
0710         NodeImpl *node = start.node();
0711         while (1) {
0712             if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
0713                 NodeImpl *runStart = node;
0714                 while (1) {
0715                     if (runStart->parentNode() != node->parentNode() || node->isHTMLElement() || node == end.node() ||
0716                             (node->renderer() && !node->renderer()->isInline())) {
0717                         applyStyleIfNeeded(runStart, node);
0718                         break;
0719                     }
0720                     node = node->traverseNextNode();
0721                 }
0722             }
0723             if (node == end.node()) {
0724                 break;
0725             }
0726             node = node->traverseNextNode();
0727         }
0728     }
0729 }
0730 
0731 //------------------------------------------------------------------------------------------
0732 // ApplyStyleCommandImpl: style-removal helpers
0733 
0734 bool ApplyStyleCommandImpl::isHTMLStyleNode(HTMLElementImpl *elem)
0735 {
0736     QListIterator<CSSProperty *> it(*(style()->values()));
0737     while (it.hasNext()) {
0738         CSSProperty *property = it.next();
0739         switch (property->id()) {
0740         case CSS_PROP_FONT_WEIGHT:
0741             if (elem->id() == ID_B) {
0742                 return true;
0743             }
0744             break;
0745         case CSS_PROP_FONT_STYLE:
0746             if (elem->id() == ID_I) {
0747                 return true;
0748             }
0749             break;
0750         }
0751     }
0752 
0753     return false;
0754 }
0755 
0756 void ApplyStyleCommandImpl::removeHTMLStyleNode(HTMLElementImpl *elem)
0757 {
0758     // This node can be removed.
0759     // EDIT FIXME: This does not handle the case where the node
0760     // has attributes. But how often do people add attributes to <B> tags?
0761     // Not so often I think.
0762     assert(elem);
0763     removeNodePreservingChildren(elem);
0764 }
0765 
0766 void ApplyStyleCommandImpl::removeCSSStyle(HTMLElementImpl *elem)
0767 {
0768     assert(elem);
0769 
0770     CSSStyleDeclarationImpl *decl = elem->inlineStyleDecls();
0771     if (!decl) {
0772         return;
0773     }
0774 
0775     QListIterator<CSSProperty *> it(*(style()->values()));
0776     while (it.hasNext()) {
0777         CSSProperty *property = it.next();
0778         if (decl->getPropertyCSSValue(property->id())) {
0779             removeCSSProperty(decl, property->id());
0780         }
0781     }
0782 
0783     if (elem->id() == ID_SPAN) {
0784         // Check to see if the span is one we added to apply style.
0785         // If it is, and there are no more attributes on the span other than our
0786         // class marker, remove the span.
0787         NamedAttrMapImpl *map = elem->attributes();
0788         if (map && (map->length() == 1 || (map->length() == 2 && elem->getAttribute(ATTR_STYLE).isEmpty())) &&
0789                 elem->getAttribute(ATTR_CLASS) == styleSpanClassString()) {
0790             removeNodePreservingChildren(elem);
0791         }
0792     }
0793 }
0794 
0795 void ApplyStyleCommandImpl::removeStyle(const Position &start, const Position &end)
0796 {
0797     NodeImpl *node = start.node();
0798     while (1) {
0799         NodeImpl *next = node->traverseNextNode();
0800         if (node->isHTMLElement() && nodeFullySelected(node)) {
0801             HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
0802             if (isHTMLStyleNode(elem)) {
0803                 removeHTMLStyleNode(elem);
0804             } else {
0805                 removeCSSStyle(elem);
0806             }
0807         }
0808         if (node == end.node()) {
0809             break;
0810         }
0811         node = next;
0812     }
0813 }
0814 
0815 bool ApplyStyleCommandImpl::nodeFullySelected(const NodeImpl *node) const
0816 {
0817     assert(node);
0818 
0819     Position end(endingSelection().end().equivalentUpstreamPosition());
0820 
0821     if (node == end.node()) {
0822         return end.offset() >= node->caretMaxOffset();
0823     }
0824 
0825     for (NodeImpl *child = node->lastChild(); child; child = child->lastChild()) {
0826         if (child == end.node()) {
0827             return end.offset() >= child->caretMaxOffset();
0828         }
0829     }
0830 
0831     return node == end.node() || !node->isAncestor(end.node());
0832 }
0833 
0834 //------------------------------------------------------------------------------------------
0835 // ApplyStyleCommandImpl: style-application helpers
0836 
0837 bool ApplyStyleCommandImpl::splitTextAtStartIfNeeded(const Position &start, const Position &end)
0838 {
0839     if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
0840 #ifdef DEBUG_COMMANDS
0841         qCDebug(KHTML_LOG) << "[split start]" << start.offset() << start.node()->caretMinOffset() << start.node()->caretMaxOffset();
0842 #endif
0843         long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
0844         TextImpl *text = static_cast<TextImpl *>(start.node());
0845         RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, start.offset());
0846         applyCommandToComposite(cmd);
0847         setEndingSelection(Selection(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment)));
0848         return true;
0849     }
0850     return false;
0851 }
0852 
0853 NodeImpl *ApplyStyleCommandImpl::splitTextAtEndIfNeeded(const Position &start, const Position &end)
0854 {
0855     if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
0856 #ifdef DEBUG_COMMANDS
0857         qCDebug(KHTML_LOG) << "[split end]" << end.offset() << end.node()->caretMinOffset() << end.node()->caretMaxOffset();
0858 #endif
0859         TextImpl *text = static_cast<TextImpl *>(end.node());
0860         RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, end.offset());
0861         applyCommandToComposite(cmd);
0862         NodeImpl *startNode = start.node() == end.node() ? cmd->node()->previousSibling() : start.node();
0863         assert(startNode);
0864         setEndingSelection(Selection(Position(startNode, start.offset()), Position(cmd->node()->previousSibling(), cmd->node()->previousSibling()->caretMaxOffset())));
0865         return cmd->node()->previousSibling();
0866     }
0867     return end.node();
0868 }
0869 
0870 void ApplyStyleCommandImpl::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
0871 {
0872     assert(startNode);
0873     assert(endNode);
0874     assert(element);
0875 
0876     NodeImpl *node = startNode;
0877     while (1) {
0878         NodeImpl *next = node->traverseNextNode();
0879         if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
0880             removeNode(node);
0881             appendNode(element, node);
0882         }
0883         if (node == endNode) {
0884             break;
0885         }
0886         node = next;
0887     }
0888 }
0889 
0890 static bool /*ApplyStyleCommandImpl::*/checkIfNewStylingNeeded(ElementImpl *element, CSSStyleDeclarationImpl *style)
0891 {
0892     QScopedPointer<CSSStyleDeclarationImpl> computedStyle(
0893             element->document()->defaultView()->getComputedStyle(element, nullptr));
0894     assert(!computedStyle.isNull());
0895 #ifdef DEBUG_COMMANDS
0896     qCDebug(KHTML_LOG) << "[check styling]" << element;
0897 #endif
0898 
0899     QListIterator<CSSProperty *> it(*(style->values()));
0900     while (it.hasNext()) {
0901         CSSProperty *property = it.next();
0902         CSSValueImpl *computedValue = computedStyle->getPropertyCSSValue(property->id());
0903         DOMString newValue = property->value()->cssText();
0904 #ifdef DEBUG_COMMANDS
0905         qCDebug(KHTML_LOG) << "[new value]:" << property->cssText();
0906         qCDebug(KHTML_LOG) << "[computedValue]:" << computedValue->cssText();
0907 #endif
0908         if (strcasecmp(computedValue->cssText(), newValue)) {
0909             return true;
0910         }
0911     }
0912     return false;
0913 }
0914 
0915 void ApplyStyleCommandImpl::applyStyleIfNeeded(DOM::NodeImpl *startNode, DOM::NodeImpl *endNode)
0916 {
0917     ElementImpl *parent = Position(startNode, 0).element();
0918     if (!checkIfNewStylingNeeded(parent, style())) {
0919         return;
0920     }
0921     ElementImpl *styleElement = nullptr;
0922     if (parent->id() == ID_SPAN && parent->firstChild() == startNode && parent->lastChild() == endNode) {
0923         styleElement = parent;
0924     } else {
0925         styleElement = document()->createHTMLElement("SPAN");
0926         styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
0927         insertNodeBefore(styleElement, startNode);
0928         surroundNodeRangeWithElement(startNode, endNode, styleElement);
0929     }
0930     applyStyleChangeOnTheNode(styleElement, style());
0931 }
0932 
0933 bool ApplyStyleCommandImpl::currentlyHasStyle(const Position &pos, const CSSProperty *property) const
0934 {
0935     assert(pos.notEmpty());
0936     qCDebug(KHTML_LOG) << pos;
0937     CSSStyleDeclarationImpl *decl = document()->defaultView()->getComputedStyle(pos.element(), nullptr);
0938     assert(decl);
0939     CSSValueImpl *value = decl->getPropertyCSSValue(property->id());
0940     return strcasecmp(value->cssText(), property->value()->cssText()) == 0;
0941 }
0942 
0943 ApplyStyleCommandImpl::StyleChange ApplyStyleCommandImpl::computeStyleChange(const Position &insertionPoint, CSSStyleDeclarationImpl *style)
0944 {
0945     assert(insertionPoint.notEmpty());
0946     assert(style);
0947 
0948     StyleChange styleChange;
0949 
0950     QListIterator<CSSProperty *> it(*(style->values()));
0951     while (it.hasNext()) {
0952         CSSProperty *property = it.next();
0953 #ifdef DEBUG_COMMANDS
0954         qCDebug(KHTML_LOG) << "[CSS property]:" << property->cssText();
0955 #endif
0956         if (!currentlyHasStyle(insertionPoint, property)) {
0957 #ifdef DEBUG_COMMANDS
0958             qCDebug(KHTML_LOG) << "[Add to style change]";
0959 #endif
0960             switch (property->id()) {
0961             case CSS_PROP_FONT_WEIGHT:
0962                 if (strcasecmp(property->value()->cssText(), "bold") == 0) {
0963                     styleChange.applyBold = true;
0964                 } else {
0965                     styleChange.cssStyle += property->cssText();
0966                 }
0967                 break;
0968             case CSS_PROP_FONT_STYLE: {
0969                 DOMString cssText(property->value()->cssText());
0970                 if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0) {
0971                     styleChange.applyItalic = true;
0972                 } else {
0973                     styleChange.cssStyle += property->cssText();
0974                 }
0975             }
0976             break;
0977             default:
0978                 styleChange.cssStyle += property->cssText();
0979                 break;
0980             }
0981         }
0982     }
0983     return styleChange;
0984 }
0985 
0986 Position ApplyStyleCommandImpl::positionInsertionPoint(Position pos)
0987 {
0988     if (pos.node()->isTextNode() && (pos.offset() > 0 && pos.offset() < pos.node()->maxOffset())) {
0989         RefPtr<SplitTextNodeCommandImpl> split = new SplitTextNodeCommandImpl(document(), static_cast<TextImpl *>(pos.node()), pos.offset());
0990         split->apply();
0991         pos = Position(split->node(), 0);
0992     }
0993 #if 0
0994     // EDIT FIXME: If modified to work with the internals of applying style,
0995     // this code can work to optimize cases where a style change is taking place on
0996     // a boundary between nodes where one of the nodes has the desired style. In other
0997     // words, it is possible for content to be merged into existing nodes rather than adding
0998     // additional markup.
0999     if (currentlyHasStyle(pos)) {
1000         return pos;
1001     }
1002 
1003     // try next node
1004     if (pos.offset() >= pos.node()->caretMaxOffset()) {
1005         NodeImpl *nextNode = pos.node()->traverseNextNode();
1006         if (nextNode) {
1007             Position next = Position(nextNode, 0);
1008             if (currentlyHasStyle(next)) {
1009                 return next;
1010             }
1011         }
1012     }
1013 
1014     // try previous node
1015     if (pos.offset() <= pos.node()->caretMinOffset()) {
1016         NodeImpl *prevNode = pos.node()->traversePreviousNode();
1017         if (prevNode) {
1018             Position prev = Position(prevNode, prevNode->maxOffset());
1019             if (currentlyHasStyle(prev)) {
1020                 return prev;
1021             }
1022         }
1023     }
1024 #endif
1025 
1026     return pos;
1027 }
1028 
1029 //------------------------------------------------------------------------------------------
1030 // DeleteCollapsibleWhitespaceCommandImpl
1031 
1032 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document)
1033     : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_hasSelectionToCollapse(false)
1034 {
1035 }
1036 
1037 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document, const Selection &selection)
1038     : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_selectionToCollapse(selection), m_hasSelectionToCollapse(true)
1039 {
1040 }
1041 
1042 DeleteCollapsibleWhitespaceCommandImpl::~DeleteCollapsibleWhitespaceCommandImpl()
1043 {
1044 }
1045 
1046 static bool shouldDeleteUpstreamPosition(const Position &pos)
1047 {
1048     if (!pos.node()->isTextNode()) {
1049         return false;
1050     }
1051 
1052     RenderObject *renderer = pos.node()->renderer();
1053     if (!renderer) {
1054         return true;
1055     }
1056 
1057     TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1058     if (pos.offset() >= (long)textNode->length()) {
1059         return false;
1060     }
1061 
1062     if (pos.isLastRenderedPositionInEditableBlock()) {
1063         return false;
1064     }
1065 
1066     if (pos.isFirstRenderedPositionOnLine() || pos.isLastRenderedPositionOnLine()) {
1067         return false;
1068     }
1069 
1070     return false;
1071     // TODO we need to match DOM - Rendered offset first
1072 //    RenderText *textRenderer = static_cast<RenderText *>(renderer);
1073 //    for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
1074 //        if (pos.offset() < box->m_start) {
1075 //            return true;
1076 //        }
1077 //        if (pos.offset() >= box->m_start && pos.offset() < box->m_start + box->m_len)
1078 //            return false;
1079 //    }
1080 //
1081 //    return true;
1082 }
1083 
1084 Position DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace(const Position &pos)
1085 {
1086     Position upstream = pos.equivalentUpstreamPosition();
1087     Position downstream = pos.equivalentDownstreamPosition();
1088 #ifdef DEBUG_COMMANDS
1089     qCDebug(KHTML_LOG) << "[pos]" << pos;
1090     qCDebug(KHTML_LOG) << "[upstream:downstream]" << upstream << downstream;
1091     printEnclosingBlockTree(pos.node());
1092 #endif
1093 
1094     bool del = shouldDeleteUpstreamPosition(upstream);
1095 #ifdef DEBUG_COMMANDS
1096     qCDebug(KHTML_LOG) << "[delete upstream]" << del;
1097 #endif
1098 
1099     if (upstream == downstream) {
1100         return upstream;
1101     }
1102 
1103 #ifdef DEBUG_COMMANDS
1104     PositionIterator iter(upstream);
1105     qCDebug(KHTML_LOG) << "[before print]";
1106     for (iter.next(); iter.current() != downstream; iter.next()) {
1107         qCDebug(KHTML_LOG) << "[iterate]" << iter.current();
1108     }
1109     qCDebug(KHTML_LOG) << "[after print]";
1110 #endif
1111 
1112     PositionIterator it(upstream);
1113     Position deleteStart = upstream;
1114     if (!del) {
1115         deleteStart = it.peekNext();
1116         if (deleteStart == downstream) {
1117             return upstream;
1118         }
1119     }
1120 
1121     Position endingPosition = upstream;
1122 
1123     while (it.current() != downstream) {
1124         Position next = it.peekNext();
1125 #ifdef DEBUG_COMMANDS
1126         qCDebug(KHTML_LOG) << "[iterate and delete]" << next;
1127 #endif
1128         if (next.node() != deleteStart.node()) {
1129             // TODO assert(deleteStart.node()->isTextNode());
1130             if (deleteStart.node()->isTextNode()) {
1131                 TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node());
1132                 unsigned long count = it.current().offset() - deleteStart.offset();
1133                 if (count == textNode->length()) {
1134 #ifdef DEBUG_COMMANDS
1135                     qCDebug(KHTML_LOG) << "   removeNodeAndPrune 1:" << textNode;
1136 #endif
1137                     if (textNode == endingPosition.node()) {
1138                         endingPosition = Position(next.node(), next.node()->caretMinOffset());
1139                     }
1140                     removeNodeAndPrune(textNode);
1141                 } else {
1142 #ifdef DEBUG_COMMANDS
1143                     qCDebug(KHTML_LOG) << "   deleteText 1:" <<  textNode << "t len:" << textNode->length() << "start:" <<  deleteStart.offset() << "del len:" << (it.current().offset() - deleteStart.offset());
1144 #endif
1145                     deleteText(textNode, deleteStart.offset(), count);
1146                 }
1147             } else {
1148 #ifdef DEBUG_COMMANDS
1149                 qCDebug(KHTML_LOG) << "[not text node is not supported yet]";
1150 #endif
1151             }
1152             deleteStart = next;
1153         } else if (next == downstream) {
1154             assert(deleteStart.node() == downstream.node());
1155             assert(downstream.node()->isTextNode());
1156             TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node());
1157             unsigned long count = downstream.offset() - deleteStart.offset();
1158             assert(count <= textNode->length());
1159             if (count == textNode->length()) {
1160 #ifdef DEBUG_COMMANDS
1161                 qCDebug(KHTML_LOG) << "   removeNodeAndPrune 2:" << textNode;
1162 #endif
1163                 removeNodeAndPrune(textNode);
1164             } else {
1165 #ifdef DEBUG_COMMANDS
1166                 qCDebug(KHTML_LOG) << "   deleteText 2:" << textNode << "t len:" <<  textNode->length() << "start:" << deleteStart.offset() << "del len:" <<  count;
1167 #endif
1168                 deleteText(textNode, deleteStart.offset(), count);
1169                 m_charactersDeleted = count;
1170                 endingPosition = Position(downstream.node(), downstream.offset() - m_charactersDeleted);
1171             }
1172         }
1173 
1174         it.setPosition(next);
1175     }
1176 
1177     return endingPosition;
1178 }
1179 
1180 void DeleteCollapsibleWhitespaceCommandImpl::doApply()
1181 {
1182     // If selection has not been set to a custom selection when the command was created,
1183     // use the current ending selection.
1184     if (!m_hasSelectionToCollapse) {
1185         m_selectionToCollapse = endingSelection();
1186     }
1187     int state = m_selectionToCollapse.state();
1188     if (state == Selection::CARET) {
1189         Position endPosition = deleteWhitespace(m_selectionToCollapse.start());
1190         setEndingSelection(endPosition);
1191 #ifdef DEBUG_COMMANDS
1192         qCDebug(KHTML_LOG) << "-----------------------------------------------------";
1193 #endif
1194     } else if (state == Selection::RANGE) {
1195         Position startPosition = deleteWhitespace(m_selectionToCollapse.start());
1196 #ifdef DEBUG_COMMANDS
1197         qCDebug(KHTML_LOG) <<  "-----------------------------------------------------";
1198 #endif
1199         Position endPosition = m_selectionToCollapse.end();
1200         if (m_charactersDeleted > 0 && startPosition.node() == endPosition.node()) {
1201 #ifdef DEBUG_COMMANDS
1202             qCDebug(KHTML_LOG) << "adjust end position by" << m_charactersDeleted;
1203 #endif
1204             endPosition = Position(endPosition.node(), endPosition.offset() - m_charactersDeleted);
1205         }
1206         endPosition = deleteWhitespace(endPosition);
1207         setEndingSelection(Selection(startPosition, endPosition));
1208 #ifdef DEBUG_COMMANDS
1209         qCDebug(KHTML_LOG) << "=====================================================";
1210 #endif
1211     }
1212 }
1213 
1214 //------------------------------------------------------------------------------------------
1215 // DeleteSelectionCommandImpl
1216 
1217 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document)
1218     : CompositeEditCommandImpl(document), m_hasSelectionToDelete(false)
1219 {
1220 }
1221 
1222 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document, const Selection &selection)
1223     : CompositeEditCommandImpl(document), m_selectionToDelete(selection), m_hasSelectionToDelete(true)
1224 {
1225 }
1226 
1227 DeleteSelectionCommandImpl::~DeleteSelectionCommandImpl()
1228 {
1229 }
1230 
1231 void DeleteSelectionCommandImpl::joinTextNodesWithSameStyle()
1232 {
1233     Selection selection = endingSelection();
1234 
1235     if (selection.state() != Selection::CARET) {
1236         return;
1237     }
1238 
1239     Position pos(selection.start());
1240 
1241     if (!pos.node()->isTextNode()) {
1242         return;
1243     }
1244 
1245     TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1246 
1247     if (pos.offset() == 0) {
1248         PositionIterator it(pos);
1249         Position prev = it.previous();
1250         if (prev == pos) {
1251             return;
1252         }
1253         if (prev.node()->isTextNode()) {
1254             TextImpl *prevTextNode = static_cast<TextImpl *>(prev.node());
1255             if (textNodesAreJoinable(prevTextNode, textNode)) {
1256                 joinTextNodes(prevTextNode, textNode);
1257                 setEndingSelection(Position(textNode, prevTextNode->length()));
1258 #ifdef DEBUG_COMMANDS
1259                 qCDebug(KHTML_LOG) << "joinTextNodesWithSameStyle [1]";
1260 #endif
1261             }
1262         }
1263     } else if (pos.offset() == (long)textNode->length()) {
1264         PositionIterator it(pos);
1265         Position next = it.next();
1266         if (next == pos) {
1267             return;
1268         }
1269         if (next.node()->isTextNode()) {
1270             TextImpl *nextTextNode = static_cast<TextImpl *>(next.node());
1271             if (textNodesAreJoinable(textNode, nextTextNode)) {
1272                 joinTextNodes(textNode, nextTextNode);
1273                 setEndingSelection(Position(nextTextNode, pos.offset()));
1274 #ifdef DEBUG_COMMANDS
1275                 qCDebug(KHTML_LOG) << "joinTextNodesWithSameStyle [2]";
1276 #endif
1277             }
1278         }
1279     }
1280 }
1281 
1282 bool DeleteSelectionCommandImpl::containsOnlyWhitespace(const Position &start, const Position &end)
1283 {
1284     // Returns whether the range contains only whitespace characters.
1285     // This is inclusive of the start, but not of the end.
1286     PositionIterator it(start);
1287     while (!it.atEnd()) {
1288         if (!it.current().node()->isTextNode()) {
1289             return false;
1290         }
1291         const DOMString &text = static_cast<TextImpl *>(it.current().node())->data();
1292         // EDIT FIXME: signed/unsigned mismatch
1293         if (text.length() > INT_MAX) {
1294             return false;
1295         }
1296         if (it.current().offset() < (int)text.length() && !isWS(text[it.current().offset()])) {
1297             return false;
1298         }
1299         it.next();
1300         if (it.current() == end) {
1301             break;
1302         }
1303     }
1304     return true;
1305 }
1306 
1307 void DeleteSelectionCommandImpl::deleteContentInsideNode(NodeImpl *node, int startOffset, int endOffset)
1308 {
1309 #ifdef DEBUG_COMMANDS
1310     qCDebug(KHTML_LOG) << "[Delete content inside node]" << node << startOffset << endOffset;
1311 #endif
1312     if (node->isTextNode()) {
1313         // check if nothing to delete
1314         if (startOffset == endOffset) {
1315             return;
1316         }
1317         // check if node is fully covered then remove node completely
1318         if (!startOffset && endOffset == node->maxOffset()) {
1319             removeNodeAndPrune(node);
1320             return;
1321         }
1322         // delete only substring
1323         deleteText(static_cast<TextImpl *>(node), startOffset, endOffset - startOffset);
1324         return;
1325     }
1326 #ifdef DEBUG_COMMANDS
1327     qCDebug(KHTML_LOG) << "[non-text node] not supported";
1328 #endif
1329 }
1330 
1331 void DeleteSelectionCommandImpl::deleteContentBeforeOffset(NodeImpl *node, int offset)
1332 {
1333     deleteContentInsideNode(node, 0, offset);
1334 }
1335 
1336 void DeleteSelectionCommandImpl::deleteContentAfterOffset(NodeImpl *node, int offset)
1337 {
1338     if (node->isTextNode()) {
1339         deleteContentInsideNode(node, offset, node->maxOffset());
1340     }
1341 }
1342 
1343 void DeleteSelectionCommandImpl::doApply()
1344 {
1345     // If selection has not been set to a custom selection when the command was created,
1346     // use the current ending selection.
1347     if (!m_hasSelectionToDelete) {
1348         m_selectionToDelete = endingSelection();
1349     }
1350 
1351     if (m_selectionToDelete.state() != Selection::RANGE) {
1352         return;
1353     }
1354 
1355     deleteCollapsibleWhitespace(m_selectionToDelete);
1356     Selection selection = endingSelection();
1357 
1358     Position upstreamStart(selection.start().equivalentUpstreamPosition());
1359     Position downstreamStart(selection.start().equivalentDownstreamPosition());
1360     Position upstreamEnd(selection.end().equivalentUpstreamPosition());
1361     Position downstreamEnd(selection.end().equivalentDownstreamPosition());
1362 
1363     NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement();
1364     NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement();
1365 
1366 #ifdef DEBUG_COMMANDS
1367     qCDebug(KHTML_LOG) << "[Delete:Start]" << upstreamStart << downstreamStart;
1368     qCDebug(KHTML_LOG) << "[Delete:End]" << upstreamEnd << downstreamEnd;
1369     printEnclosingBlockTree(upstreamStart.node());
1370 #endif
1371     if (startBlock != endBlock) {
1372         printEnclosingBlockTree(downstreamEnd.node());
1373     }
1374 
1375     if (upstreamStart == downstreamEnd)
1376         // after collapsing whitespace, selection is empty...no work to do
1377     {
1378         return;
1379     }
1380 
1381     // remove all the nodes that are completely covered by the selection
1382     if (upstreamStart.node() != downstreamEnd.node()) {
1383         NodeImpl *node, *next;
1384         for (node = upstreamStart.node()->traverseNextNode(); node && node != downstreamEnd.node(); node = next) {
1385 #ifdef DEBUG_COMMANDS
1386             qCDebug(KHTML_LOG) << "[traverse and delete]" << node << (node->renderer() && node->renderer()->isEditable());
1387 #endif
1388             next = node->traverseNextNode();
1389             if (node->renderer() && node->renderer()->isEditable()) {
1390                 removeNode(node);    // removeAndPrune?
1391             }
1392         }
1393     }
1394 
1395     // if we have different blocks then merge content of the second into first one
1396     if (startBlock != endBlock && startBlock->parentNode() == endBlock->parentNode()) {
1397         NodeImpl *node = endBlock->firstChild();
1398         while (node) {
1399             NodeImpl *moveNode = node;
1400             node = node->nextSibling();
1401             removeNode(moveNode);
1402             appendNode(startBlock, moveNode);
1403         }
1404     }
1405 
1406     if (upstreamStart.node() == downstreamEnd.node()) {
1407         deleteContentInsideNode(upstreamEnd.node(), upstreamStart.offset(), downstreamEnd.offset());
1408     } else {
1409         deleteContentAfterOffset(upstreamStart.node(), upstreamStart.offset());
1410         deleteContentBeforeOffset(downstreamEnd.node(), downstreamEnd.offset());
1411     }
1412 
1413     setEndingSelection(upstreamStart);
1414 #if 0
1415     Position endingPosition;
1416     bool adjustEndingPositionDownstream = false;
1417 
1418     bool onlyWhitespace = containsOnlyWhitespace(upstreamStart, downstreamEnd);
1419     qCDebug(KHTML_LOG) << "[OnlyWhitespace]" << onlyWhitespace;
1420 
1421     bool startCompletelySelected = !onlyWhitespace &&
1422                                    (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset() &&
1423                                     ((downstreamStart.node() != upstreamEnd.node()) ||
1424                                      (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset())));
1425 
1426     bool endCompletelySelected = !onlyWhitespace &&
1427                                  (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset() &&
1428                                   ((downstreamStart.node() != upstreamEnd.node()) ||
1429                                    (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset())));
1430 
1431     qCDebug(KHTML_LOG) << "[{start:end}CompletelySelected]" << startCompletelySelected << endCompletelySelected;
1432 
1433     unsigned long startRenderedOffset = downstreamStart.renderedOffset();
1434 
1435     bool startAtStartOfRootEditableElement = startRenderedOffset == 0 && downstreamStart.inFirstEditableInRootEditableElement();
1436     bool startAtStartOfBlock = startAtStartOfRootEditableElement ||
1437                                (startRenderedOffset == 0 && downstreamStart.inFirstEditableInContainingEditableBlock());
1438     bool endAtEndOfBlock = downstreamEnd.isLastRenderedPositionInEditableBlock();
1439 
1440     qCDebug(KHTML_LOG) << "[startAtStartOfRootEditableElement]" << startAtStartOfRootEditableElement;
1441     qCDebug(KHTML_LOG) << "[startAtStartOfBlock]" << startAtStartOfBlock;
1442     qCDebug(KHTML_LOG) << "[endAtEndOfBlock]" << endAtEndOfBlock;
1443 
1444     NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement();
1445     NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement();
1446     bool startBlockEndBlockAreSiblings = startBlock->parentNode() == endBlock->parentNode();
1447 
1448     qCDebug(KHTML_LOG) << "[startBlockEndBlockAreSiblings]" << startBlockEndBlockAreSiblings << startBlock << endBlock;
1449 
1450     debugPosition("upstreamStart:       ", upstreamStart);
1451     debugPosition("downstreamStart:     ", downstreamStart);
1452     debugPosition("upstreamEnd:         ", upstreamEnd);
1453     debugPosition("downstreamEnd:       ", downstreamEnd);
1454     qCDebug(KHTML_LOG) << "start selected:" << (startCompletelySelected ? "YES" : "NO");
1455     qCDebug(KHTML_LOG) << "at start block:" << (startAtStartOfBlock ? "YES" : "NO");
1456     qCDebug(KHTML_LOG) << "at start root block:" << (startAtStartOfRootEditableElement ? "YES" : "NO");
1457     qCDebug(KHTML_LOG) << "at end block:" << (endAtEndOfBlock ? "YES" : "NO");
1458     qCDebug(KHTML_LOG) << "only whitespace:" << (onlyWhitespace ? "YES" : "NO");
1459 
1460     // Determine where to put the caret after the deletion
1461     if (startAtStartOfBlock) {
1462         qCDebug(KHTML_LOG) << "ending position case 1";
1463         endingPosition = Position(startBlock, 0);
1464         adjustEndingPositionDownstream = true;
1465     } else if (!startCompletelySelected) {
1466         qCDebug(KHTML_LOG) << "ending position case 2";
1467         endingPosition = upstreamEnd; // FIXME ??????????? upstreamStart;
1468         if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1) {
1469             adjustEndingPositionDownstream = true;
1470         }
1471     } else if (upstreamStart != downstreamStart) {
1472         qCDebug(KHTML_LOG) << "ending position case 3";
1473         endingPosition = upstreamStart;
1474         if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1) {
1475             adjustEndingPositionDownstream = true;
1476         }
1477     }
1478 
1479     //
1480     // Figure out the whitespace conversions to do
1481     //
1482     if ((startAtStartOfBlock && !endAtEndOfBlock) || (!startCompletelySelected && adjustEndingPositionDownstream)) {
1483         // convert trailing whitespace
1484         Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
1485         if (trailing.notEmpty()) {
1486             debugPosition("convertTrailingWhitespace: ", trailing);
1487             Position collapse = trailing.nextCharacterPosition();
1488             if (collapse != trailing) {
1489                 deleteCollapsibleWhitespace(collapse);
1490             }
1491             TextImpl *textNode = static_cast<TextImpl *>(trailing.node());
1492             replaceText(textNode, trailing.offset(), 1, nonBreakingSpaceString());
1493         }
1494     } else if (!startAtStartOfBlock && endAtEndOfBlock) {
1495         // convert leading whitespace
1496         Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
1497         if (leading.notEmpty()) {
1498             debugPosition("convertLeadingWhitespace:  ", leading);
1499             TextImpl *textNode = static_cast<TextImpl *>(leading.node());
1500             replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
1501         }
1502     } else if (!startAtStartOfBlock && !endAtEndOfBlock) {
1503         // convert contiguous whitespace
1504         Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
1505         Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
1506         if (leading.notEmpty() && trailing.notEmpty()) {
1507             debugPosition("convertLeadingWhitespace [contiguous]:  ", leading);
1508             TextImpl *textNode = static_cast<TextImpl *>(leading.node());
1509             replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
1510         }
1511     }
1512 
1513     //
1514     // Do the delete
1515     //
1516     NodeImpl *n = downstreamStart.node()->traverseNextNode();
1517     qCDebug(KHTML_LOG) << "[n]" << n;
1518 
1519     // work on start node
1520     if (startCompletelySelected) {
1521         qCDebug(KHTML_LOG) << "start node delete case 1";
1522         removeNodeAndPrune(downstreamStart.node(), startBlock);
1523     } else if (onlyWhitespace) {
1524         // Selection only contains whitespace. This is really a special-case to
1525         // handle significant whitespace that is collapsed at the end of a line,
1526         // but also handles deleting a space in mid-line.
1527         qCDebug(KHTML_LOG) << "start node delete case 2";
1528         assert(upstreamStart.node()->isTextNode());
1529         TextImpl *text = static_cast<TextImpl *>(upstreamStart.node());
1530         int offset = upstreamStart.offset();
1531         // EDIT FIXME: Signed/unsigned mismatch
1532         int length = text->length();
1533         if (length == upstreamStart.offset()) {
1534             offset--;
1535         }
1536         // FIXME ??? deleteText(text, offset, 1);
1537     } else if (downstreamStart.node()->isTextNode()) {
1538         qCDebug(KHTML_LOG) << "start node delete case 3";
1539         TextImpl *text = static_cast<TextImpl *>(downstreamStart.node());
1540         int endOffset = text == upstreamEnd.node() ? upstreamEnd.offset() : text->length();
1541         if (endOffset > downstreamStart.offset()) {
1542             deleteText(text, downstreamStart.offset(), endOffset - downstreamStart.offset());
1543         }
1544     } else {
1545         // we have clipped the end of a non-text element
1546         // the offset must be 1 here. if it is, do nothing and move on.
1547         qCDebug(KHTML_LOG) << "start node delete case 4";
1548         assert(downstreamStart.offset() == 1);
1549     }
1550 
1551     if (n && !onlyWhitespace && downstreamStart.node() != upstreamEnd.node()) {
1552         // work on intermediate nodes
1553         while (n && n != upstreamEnd.node()) {
1554             NodeImpl *d = n;
1555             n = n->traverseNextNode();
1556             if (d->renderer() && d->renderer()->isEditable()) {
1557                 removeNodeAndPrune(d, startBlock);
1558             }
1559         }
1560         if (!n) {
1561             return;
1562         }
1563 
1564         // work on end node
1565         assert(n == upstreamEnd.node());
1566         if (endCompletelySelected) {
1567             removeNodeAndPrune(upstreamEnd.node(), startBlock);
1568         } else if (upstreamEnd.node()->isTextNode()) {
1569             if (upstreamEnd.offset() > 0) {
1570                 TextImpl *text = static_cast<TextImpl *>(upstreamEnd.node());
1571                 deleteText(text, 0, upstreamEnd.offset());
1572             }
1573         } else {
1574             // we have clipped the beginning of a non-text element
1575             // the offset must be 0 here. if it is, do nothing and move on.
1576             assert(downstreamStart.offset() == 0);
1577         }
1578     }
1579 
1580     // Do block merge if start and end of selection are in different blocks
1581     // and the blocks are siblings. This is a first cut at this rule arrived
1582     // at by doing a bunch of edits and settling on the behavior that made
1583     // the most sense. This could change in the future as we get more
1584     // experience with how this should behave.
1585     if (startBlock != endBlock && startBlockEndBlockAreSiblings) {
1586         qCDebug(KHTML_LOG) << "merging content to start block";
1587         NodeImpl *node = endBlock->firstChild();
1588         while (node) {
1589             NodeImpl *moveNode = node;
1590             node = node->nextSibling();
1591             removeNode(moveNode);
1592             appendNode(startBlock, moveNode);
1593         }
1594     }
1595 
1596     if (adjustEndingPositionDownstream) {
1597         qCDebug(KHTML_LOG) << "adjust ending position downstream";
1598         endingPosition = endingPosition.equivalentDownstreamPosition();
1599     }
1600 
1601     debugPosition("ending position:     ", endingPosition);
1602     setEndingSelection(endingPosition);
1603 
1604     qCDebug(KHTML_LOG) << "-----------------------------------------------------";
1605 #endif
1606 }
1607 
1608 //------------------------------------------------------------------------------------------
1609 // DeleteTextCommandImpl
1610 
1611 DeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, long count)
1612     : EditCommandImpl(document), m_node(node), m_offset(offset), m_count(count)
1613 {
1614     assert(m_node);
1615     assert(m_offset >= 0);
1616     assert(m_count >= 0);
1617 
1618     m_node->ref();
1619 }
1620 
1621 DeleteTextCommandImpl::~DeleteTextCommandImpl()
1622 {
1623     if (m_node) {
1624         m_node->deref();
1625     }
1626 }
1627 
1628 void DeleteTextCommandImpl::doApply()
1629 {
1630     assert(m_node);
1631 
1632     int exceptionCode = 0;
1633     m_text = m_node->substringData(m_offset, m_count, exceptionCode);
1634     assert(exceptionCode == 0);
1635 
1636     m_node->deleteData(m_offset, m_count, exceptionCode);
1637     assert(exceptionCode == 0);
1638 }
1639 
1640 void DeleteTextCommandImpl::doUnapply()
1641 {
1642     assert(m_node);
1643     assert(!m_text.isEmpty());
1644 
1645     int exceptionCode = 0;
1646     m_node->insertData(m_offset, m_text, exceptionCode);
1647     assert(exceptionCode == 0);
1648 }
1649 
1650 //------------------------------------------------------------------------------------------
1651 // InputNewlineCommandImpl
1652 
1653 InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document)
1654     : CompositeEditCommandImpl(document)
1655 {
1656 }
1657 
1658 InputNewlineCommandImpl::~InputNewlineCommandImpl()
1659 {
1660 }
1661 
1662 void InputNewlineCommandImpl::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
1663 {
1664     // Insert the BR after the caret position. In the case the
1665     // position is a block, do an append. We don't want to insert
1666     // the BR *after* the block.
1667     Position upstream(pos.equivalentUpstreamPosition());
1668     NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
1669     if (cb == pos.node()) {
1670         appendNode(cb, node);
1671     } else {
1672         insertNodeAfter(node, pos.node());
1673     }
1674 }
1675 
1676 void InputNewlineCommandImpl::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
1677 {
1678     // Insert the BR after the caret position. In the case the
1679     // position is a block, do an append. We don't want to insert
1680     // the BR *before* the block.
1681     Position upstream(pos.equivalentUpstreamPosition());
1682     NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
1683     if (cb == pos.node()) {
1684         appendNode(cb, node);
1685     } else {
1686         insertNodeBefore(node, pos.node());
1687     }
1688 }
1689 
1690 void InputNewlineCommandImpl::doApply()
1691 {
1692     deleteSelection();
1693     Selection selection = endingSelection();
1694     int exceptionCode = 0;
1695 
1696     NodeImpl *enclosingBlock = selection.start().node()->enclosingBlockFlowElement();
1697     qCDebug(KHTML_LOG) << enclosingBlock->nodeName();
1698     if (enclosingBlock->id() == ID_LI) {
1699         // need to insert new list item or split existing one into 2
1700         // consider example: <li>x<u>x<b>x|x</b>x</u>x</li> (| - caret position)
1701         // result should look like: <li>x<u>x<b>x</b></u></li><li><u>|x<b>x</b></u></li>
1702         // idea is to walk up to the li item and split and reattach correspondent nodes
1703 #ifdef DEBUG_COMMANDS
1704         qCDebug(KHTML_LOG) << "[insert new list item]" << selection;
1705         printEnclosingBlockTree(selection.start().node());
1706 #endif
1707         Position pos(selection.start().equivalentDownstreamPosition());
1708         NodeImpl *node = pos.node();
1709         bool atBlockStart = pos.atStartOfContainingEditableBlock();
1710         bool atBlockEnd = pos.isLastRenderedPositionInEditableBlock();
1711         // split text node into 2 if we are in the middle
1712         if (node->isTextNode() && !atBlockStart && !atBlockEnd) {
1713             TextImpl *textNode = static_cast<TextImpl *>(node);
1714             TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
1715             deleteText(textNode, 0, pos.offset());
1716             insertNodeBefore(textBeforeNode, textNode);
1717             pos = Position(textNode, 0);
1718             setEndingSelection(pos);
1719 
1720             // walk up and reattach
1721             while (true) {
1722 #ifdef DEBUG_COMMANDS
1723                 qCDebug(KHTML_LOG) << "[handle node]" << node;
1724                 printEnclosingBlockTree(enclosingBlock->parent());
1725 #endif
1726                 NodeImpl *parent = node->parent();
1727                 // FIXME copy attributes, styles etc too
1728                 RefPtr<NodeImpl> newParent = parent->cloneNode(false);
1729                 insertNodeAfter(newParent.get(), parent);
1730                 for (NodeImpl *nextSibling = nullptr; node; node = nextSibling) {
1731 #ifdef DEBUG_COMMANDS
1732                     qCDebug(KHTML_LOG) << "[reattach sibling]" << node;
1733 #endif
1734                     nextSibling = node->nextSibling();
1735                     removeNode(node);
1736                     appendNode(newParent.get(), node);
1737                 }
1738                 node = newParent.get();
1739                 if (parent == enclosingBlock) {
1740                     break;
1741                 }
1742             }
1743         } else if (node->isTextNode()) {
1744             // insert <br> node either as previous list or the next one
1745             if (atBlockStart) {
1746                 ElementImpl *listItem = document()->createHTMLElement("LI");
1747                 insertNodeBefore(listItem, enclosingBlock);
1748             } else {
1749                 ElementImpl *listItem = document()->createHTMLElement("LI");
1750                 insertNodeAfter(listItem, enclosingBlock);
1751             }
1752         }
1753 
1754 #ifdef DEBUG_COMMANDS
1755         qCDebug(KHTML_LOG) << "[result]";
1756         printEnclosingBlockTree(enclosingBlock->parent());
1757 #endif
1758         // FIXME set selection after operation
1759         return;
1760     }
1761 
1762     ElementImpl *breakNode = document()->createHTMLElement("BR");
1763     // assert(exceptionCode == 0);
1764 
1765 #ifdef DEBUG_COMMANDS
1766     qCDebug(KHTML_LOG) << "[insert break]" << selection;
1767     printEnclosingBlockTree(enclosingBlock);
1768 #endif
1769 
1770     NodeImpl *nodeToInsert = breakNode;
1771     // Handle the case where there is a typing style.
1772     if (document()->part()->editor()->typingStyle()) {
1773         int exceptionCode = 0;
1774         ElementImpl *styleElement = createTypingStyleElement();
1775         styleElement->appendChild(breakNode, exceptionCode);
1776         assert(exceptionCode == 0);
1777         nodeToInsert = styleElement;
1778     }
1779 
1780     Position pos(selection.start().equivalentDownstreamPosition());
1781     bool atStart = pos.offset() <= pos.node()->caretMinOffset();
1782     bool atEndOfBlock = pos.isLastRenderedPositionInEditableBlock();
1783 
1784 #ifdef DEBUG_COMMANDS
1785     qCDebug(KHTML_LOG) << "[pos]" << pos << atStart << atEndOfBlock;
1786 #endif
1787 
1788     if (atEndOfBlock) {
1789 #ifdef DEBUG_COMMANDS
1790         qCDebug(KHTML_LOG) << "input newline case 1";
1791 #endif
1792         // Insert an "extra" BR at the end of the block. This makes the "real" BR we want
1793         // to insert appear in the rendering without any significant side effects (and no
1794         // real worries either since you can't arrow past this extra one.
1795         insertNodeAfterPosition(nodeToInsert, pos);
1796         exceptionCode = 0;
1797         ElementImpl *extraBreakNode = document()->createHTMLElement("BR");
1798 //         assert(exceptionCode == 0);
1799         insertNodeAfter(extraBreakNode, nodeToInsert);
1800         setEndingSelection(Position(extraBreakNode, 0));
1801     } else if (atStart) {
1802 #ifdef DEBUG_COMMANDS
1803         qCDebug(KHTML_LOG) << "input newline case 2";
1804 #endif
1805         // Insert node, but place the caret into index 0 of the downstream
1806         // position. This will make the caret appear after the break, and as we know
1807         // there is content at that location, this is OK.
1808         insertNodeBeforePosition(nodeToInsert, pos);
1809         setEndingSelection(Position(pos.node(), 0));
1810     } else {
1811         // Split a text node
1812         // FIXME it's possible that we create empty text node now if we're at the end of text
1813         // maybe we should handle this case specially and not create it
1814 #ifdef DEBUG_COMMANDS
1815         qCDebug(KHTML_LOG) << "input newline case 3";
1816 #endif
1817         assert(pos.node()->isTextNode());
1818         TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1819         TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
1820         deleteText(textNode, 0, selection.start().offset());
1821         insertNodeBefore(textBeforeNode, textNode);
1822         insertNodeBefore(nodeToInsert, textNode);
1823         setEndingSelection(Position(textNode, 0));
1824     }
1825 }
1826 
1827 //------------------------------------------------------------------------------------------
1828 // InputTextCommandImpl
1829 
1830 InputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document)
1831     : CompositeEditCommandImpl(document), m_charactersAdded(0)
1832 {
1833 }
1834 
1835 InputTextCommandImpl::~InputTextCommandImpl()
1836 {
1837 }
1838 
1839 void InputTextCommandImpl::doApply()
1840 {
1841 }
1842 
1843 void InputTextCommandImpl::input(const DOMString &text)
1844 {
1845     execute(text);
1846 }
1847 
1848 void InputTextCommandImpl::deleteCharacter()
1849 {
1850     assert(state() == Applied);
1851 
1852     Selection selection = endingSelection();
1853 
1854     if (!selection.start().node()->isTextNode()) {
1855         return;
1856     }
1857 
1858     int exceptionCode = 0;
1859     int offset = selection.start().offset() - 1;
1860     if (offset >= selection.start().node()->caretMinOffset()) {
1861         TextImpl *textNode = static_cast<TextImpl *>(selection.start().node());
1862         textNode->deleteData(offset, 1, exceptionCode);
1863         assert(exceptionCode == 0);
1864         selection = Selection(Position(textNode, offset));
1865         setEndingSelection(selection);
1866         m_charactersAdded--;
1867     }
1868 }
1869 
1870 Position InputTextCommandImpl::prepareForTextInsertion(bool adjustDownstream)
1871 {
1872     // Prepare for text input by looking at the current position.
1873     // It may be necessary to insert a text node to receive characters.
1874     Selection selection = endingSelection();
1875     assert(selection.state() == Selection::CARET);
1876 
1877 #ifdef DEBUG_COMMANDS
1878     qCDebug(KHTML_LOG) << "[prepare selection]" << selection;
1879 #endif
1880 
1881     Position pos = selection.start();
1882     if (adjustDownstream) {
1883         pos = pos.equivalentDownstreamPosition();
1884     } else {
1885         pos = pos.equivalentUpstreamPosition();
1886     }
1887 
1888 #ifdef DEBUG_COMMANDS
1889     qCDebug(KHTML_LOG) << "[prepare position]" << pos;
1890 #endif
1891 
1892     if (!pos.node()->isTextNode()) {
1893         NodeImpl *textNode = document()->createEditingTextNode("");
1894         NodeImpl *nodeToInsert = textNode;
1895         if (document()->part()->editor()->typingStyle()) {
1896             int exceptionCode = 0;
1897             ElementImpl *styleElement = createTypingStyleElement();
1898             styleElement->appendChild(textNode, exceptionCode);
1899             assert(exceptionCode == 0);
1900             nodeToInsert = styleElement;
1901         }
1902 
1903         // Now insert the node in the right place
1904         if (pos.node()->isEditableBlock()) {
1905             qCDebug(KHTML_LOG) << "prepareForTextInsertion case 1";
1906             appendNode(pos.node(), nodeToInsert);
1907         } else if (pos.node()->id() == ID_BR && pos.offset() == 1) {
1908             qCDebug(KHTML_LOG) << "prepareForTextInsertion case 2";
1909             insertNodeAfter(nodeToInsert, pos.node());
1910         } else if (pos.node()->caretMinOffset() == pos.offset()) {
1911             qCDebug(KHTML_LOG) << "prepareForTextInsertion case 3";
1912             insertNodeBefore(nodeToInsert, pos.node());
1913         } else if (pos.node()->caretMaxOffset() == pos.offset()) {
1914             qCDebug(KHTML_LOG) << "prepareForTextInsertion case 4";
1915             insertNodeAfter(nodeToInsert, pos.node());
1916         } else {
1917             assert(false);
1918         }
1919 
1920         pos = Position(textNode, 0);
1921     } else {
1922         // Handle the case where there is a typing style.
1923         if (document()->part()->editor()->typingStyle()) {
1924             if (pos.node()->isTextNode() && pos.offset() > pos.node()->caretMinOffset() && pos.offset() < pos.node()->caretMaxOffset()) {
1925                 // Need to split current text node in order to insert a span.
1926                 TextImpl *text = static_cast<TextImpl *>(pos.node());
1927                 RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, pos.offset());
1928                 applyCommandToComposite(cmd);
1929                 setEndingSelection(Position(cmd->node(), 0));
1930             }
1931 
1932             int exceptionCode = 0;
1933             TextImpl *editingTextNode = document()->createEditingTextNode("");
1934 
1935             ElementImpl *styleElement = createTypingStyleElement();
1936             styleElement->appendChild(editingTextNode, exceptionCode);
1937             assert(exceptionCode == 0);
1938 
1939             NodeImpl *node = endingSelection().start().node();
1940             if (endingSelection().start().isLastRenderedPositionOnLine()) {
1941                 insertNodeAfter(styleElement, node);
1942             } else {
1943                 insertNodeBefore(styleElement, node);
1944             }
1945             pos = Position(editingTextNode, 0);
1946         }
1947     }
1948     return pos;
1949 }
1950 
1951 void InputTextCommandImpl::execute(const DOMString &text)
1952 {
1953 #ifdef DEBUG_COMMANDS
1954     qCDebug(KHTML_LOG) << "[execute command]" << text;
1955 #endif
1956     Selection selection = endingSelection();
1957 #ifdef DEBUG_COMMANDS
1958     qCDebug(KHTML_LOG) << "[ending selection]" << selection;
1959 #endif
1960     bool adjustDownstream = selection.start().isFirstRenderedPositionOnLine();
1961 #ifdef DEBUG_COMMANDS
1962     qCDebug(KHTML_LOG) << "[adjust]" << adjustDownstream;
1963 #endif
1964 
1965 #ifdef DEBUG_COMMANDS
1966     printEnclosingBlockTree(selection.start().node());
1967 #endif
1968 
1969     // Delete the current selection, or collapse whitespace, as needed
1970     if (selection.state() == Selection::RANGE) {
1971         deleteSelection();
1972     } else {
1973         deleteCollapsibleWhitespace();
1974     }
1975 
1976 #ifdef DEBUG_COMMANDS
1977     qCDebug(KHTML_LOG) << "[after collapsible whitespace deletion]";
1978     printEnclosingBlockTree(selection.start().node());
1979 #endif
1980 
1981     // EDIT FIXME: Need to take typing style from upstream text, if any.
1982 
1983     // Make sure the document is set up to receive text
1984     Position pos = prepareForTextInsertion(adjustDownstream);
1985 #ifdef DEBUG_COMMANDS
1986     qCDebug(KHTML_LOG) << "[after prepare]" << pos;
1987 #endif
1988 
1989     TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1990     long offset = pos.offset();
1991 
1992 #ifdef DEBUG_COMMANDS
1993     qCDebug(KHTML_LOG) << "[insert at]" << textNode << offset;
1994 #endif
1995 
1996     // This is a temporary implementation for inserting adjoining spaces
1997     // into a document. We are working on a CSS-related whitespace solution
1998     // that will replace this some day.
1999     if (isWS(text)) {
2000         insertSpace(textNode, offset);
2001     } else {
2002         const DOMString &existingText = textNode->data();
2003         if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isWS(existingText[offset - 2])) {
2004             // DOM looks like this:
2005             // character nbsp caret
2006             // As we are about to insert a non-whitespace character at the caret
2007             // convert the nbsp to a regular space.
2008             // EDIT FIXME: This needs to be improved some day to convert back only
2009             // those nbsp's added by the editor to make rendering come out right.
2010             replaceText(textNode, offset - 1, 1, " ");
2011         }
2012         insertText(textNode, offset, text);
2013     }
2014     setEndingSelection(Position(textNode, offset + text.length()));
2015     m_charactersAdded += text.length();
2016 }
2017 
2018 void InputTextCommandImpl::insertSpace(TextImpl *textNode, unsigned long offset)
2019 {
2020     assert(textNode);
2021 
2022     DOMString text(textNode->data());
2023 
2024     // count up all spaces and newlines in front of the caret
2025     // delete all collapsed ones
2026     // this will work out OK since the offset we have been passed has been upstream-ized
2027     int count = 0;
2028     for (unsigned int i = offset; i < text.length(); i++) {
2029         if (isWS(text[i])) {
2030             count++;
2031         } else {
2032             break;
2033         }
2034     }
2035     if (count > 0) {
2036         // By checking the character at the downstream position, we can
2037         // check if there is a rendered WS at the caret
2038         Position pos(textNode, offset);
2039         Position downstream = pos.equivalentDownstreamPosition();
2040         if (downstream.offset() < (long)text.length() && isWS(text[downstream.offset()])) {
2041             count--;    // leave this WS in
2042         }
2043         if (count > 0) {
2044             deleteText(textNode, offset, count);
2045         }
2046     }
2047 
2048     if (offset > 0 && offset <= text.length() - 1 && !isWS(text[offset]) && !isWS(text[offset - 1])) {
2049         // insert a "regular" space
2050         insertText(textNode, offset, " ");
2051         return;
2052     }
2053 
2054     if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
2055         // DOM looks like this:
2056         // nbsp nbsp caret
2057         // insert a space between the two nbsps
2058         insertText(textNode, offset - 1, " ");
2059         return;
2060     }
2061 
2062     // insert an nbsp
2063     insertText(textNode, offset, nonBreakingSpaceString());
2064 }
2065 
2066 //------------------------------------------------------------------------------------------
2067 // InsertNodeBeforeCommandImpl
2068 
2069 InsertNodeBeforeCommandImpl::InsertNodeBeforeCommandImpl(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
2070     : EditCommandImpl(document), m_insertChild(insertChild), m_refChild(refChild)
2071 {
2072     assert(m_insertChild);
2073     m_insertChild->ref();
2074 
2075     assert(m_refChild);
2076     m_refChild->ref();
2077 }
2078 
2079 InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl()
2080 {
2081     if (m_insertChild) {
2082         m_insertChild->deref();
2083     }
2084     if (m_refChild) {
2085         m_refChild->deref();
2086     }
2087 }
2088 
2089 void InsertNodeBeforeCommandImpl::doApply()
2090 {
2091     assert(m_insertChild);
2092     assert(m_refChild);
2093     assert(m_refChild->parentNode());
2094 
2095     int exceptionCode = 0;
2096     m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
2097     assert(exceptionCode == 0);
2098 }
2099 
2100 void InsertNodeBeforeCommandImpl::doUnapply()
2101 {
2102     assert(m_insertChild);
2103     assert(m_refChild);
2104     assert(m_refChild->parentNode());
2105 
2106     int exceptionCode = 0;
2107     m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
2108     assert(exceptionCode == 0);
2109 }
2110 
2111 //------------------------------------------------------------------------------------------
2112 // InsertTextCommandImpl
2113 
2114 InsertTextCommandImpl::InsertTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
2115     : EditCommandImpl(document), m_node(node), m_offset(offset)
2116 {
2117     assert(m_node);
2118     assert(m_offset >= 0);
2119     assert(text.length() > 0);
2120 
2121     m_node->ref();
2122     m_text = text.copy(); // make a copy to ensure that the string never changes
2123 }
2124 
2125 InsertTextCommandImpl::~InsertTextCommandImpl()
2126 {
2127     if (m_node) {
2128         m_node->deref();
2129     }
2130 }
2131 
2132 void InsertTextCommandImpl::doApply()
2133 {
2134     assert(m_node);
2135     assert(!m_text.isEmpty());
2136 
2137     int exceptionCode = 0;
2138     m_node->insertData(m_offset, m_text, exceptionCode);
2139     assert(exceptionCode == 0);
2140 }
2141 
2142 void InsertTextCommandImpl::doUnapply()
2143 {
2144     assert(m_node);
2145     assert(!m_text.isEmpty());
2146 
2147     int exceptionCode = 0;
2148     m_node->deleteData(m_offset, m_text.length(), exceptionCode);
2149     assert(exceptionCode == 0);
2150 }
2151 
2152 //------------------------------------------------------------------------------------------
2153 // JoinTextNodesCommandImpl
2154 
2155 JoinTextNodesCommandImpl::JoinTextNodesCommandImpl(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
2156     : EditCommandImpl(document), m_text1(text1), m_text2(text2)
2157 {
2158     assert(m_text1);
2159     assert(m_text2);
2160     assert(m_text1->nextSibling() == m_text2);
2161     assert(m_text1->length() > 0);
2162     assert(m_text2->length() > 0);
2163 
2164     m_text1->ref();
2165     m_text2->ref();
2166 }
2167 
2168 JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl()
2169 {
2170     if (m_text1) {
2171         m_text1->deref();
2172     }
2173     if (m_text2) {
2174         m_text2->deref();
2175     }
2176 }
2177 
2178 void JoinTextNodesCommandImpl::doApply()
2179 {
2180     assert(m_text1);
2181     assert(m_text2);
2182     assert(m_text1->nextSibling() == m_text2);
2183 
2184     int exceptionCode = 0;
2185     m_text2->insertData(0, m_text1->data(), exceptionCode);
2186     assert(exceptionCode == 0);
2187 
2188     m_text2->parentNode()->removeChild(m_text1, exceptionCode);
2189     assert(exceptionCode == 0);
2190 
2191     m_offset = m_text1->length();
2192 }
2193 
2194 void JoinTextNodesCommandImpl::doUnapply()
2195 {
2196     assert(m_text2);
2197     assert(m_offset > 0);
2198 
2199     int exceptionCode = 0;
2200 
2201     m_text2->deleteData(0, m_offset, exceptionCode);
2202     assert(exceptionCode == 0);
2203 
2204     m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
2205     assert(exceptionCode == 0);
2206 
2207     assert(m_text2->previousSibling()->isTextNode());
2208     assert(m_text2->previousSibling() == m_text1);
2209 }
2210 
2211 //------------------------------------------------------------------------------------------
2212 // ReplaceSelectionCommandImpl
2213 
2214 ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement)
2215     : CompositeEditCommandImpl(document), m_fragment(fragment), m_selectReplacement(selectReplacement)
2216 {
2217 }
2218 
2219 ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl()
2220 {
2221 }
2222 
2223 void ReplaceSelectionCommandImpl::doApply()
2224 {
2225     NodeImpl *firstChild = m_fragment->firstChild();
2226     NodeImpl *lastChild = m_fragment->lastChild();
2227 
2228     Selection selection = endingSelection();
2229 
2230     // Delete the current selection, or collapse whitespace, as needed
2231     if (selection.state() == Selection::RANGE) {
2232         deleteSelection();
2233     } else {
2234         deleteCollapsibleWhitespace();
2235     }
2236 
2237     selection = endingSelection();
2238     assert(!selection.isEmpty());
2239 
2240     if (!firstChild) {
2241         // Pasting something that didn't parse or was empty.
2242         assert(!lastChild);
2243     } else if (firstChild == lastChild && firstChild->isTextNode()) {
2244         // Simple text paste. Treat as if the text were typed.
2245         Position base = selection.base();
2246         inputText(static_cast<TextImpl *>(firstChild)->data());
2247         if (m_selectReplacement) {
2248             setEndingSelection(Selection(base, endingSelection().extent()));
2249         }
2250     } else {
2251         // HTML fragment paste.
2252         NodeImpl *beforeNode = firstChild;
2253         NodeImpl *node = firstChild->nextSibling();
2254 
2255         insertNodeAt(firstChild, selection.start().node(), selection.start().offset());
2256 
2257         // Insert the nodes from the fragment
2258         while (node) {
2259             NodeImpl *next = node->nextSibling();
2260             insertNodeAfter(node, beforeNode);
2261             beforeNode = node;
2262             node = next;
2263         }
2264         assert(beforeNode);
2265 
2266         // Find the last leaf.
2267         NodeImpl *lastLeaf = lastChild;
2268         while (1) {
2269             NodeImpl *nextChild = lastLeaf->lastChild();
2270             if (!nextChild) {
2271                 break;
2272             }
2273             lastLeaf = nextChild;
2274         }
2275 
2276         if (m_selectReplacement) {
2277             // Find the first leaf.
2278             NodeImpl *firstLeaf = firstChild;
2279             while (1) {
2280                 NodeImpl *nextChild = firstLeaf->firstChild();
2281                 if (!nextChild) {
2282                     break;
2283                 }
2284                 firstLeaf = nextChild;
2285             }
2286             // Select what was inserted.
2287             setEndingSelection(Selection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset())));
2288         } else {
2289             // Place the cursor after what was inserted.
2290             setEndingSelection(Position(lastLeaf, lastLeaf->caretMaxOffset()));
2291         }
2292     }
2293 }
2294 
2295 //------------------------------------------------------------------------------------------
2296 // MoveSelectionCommandImpl
2297 
2298 MoveSelectionCommandImpl::MoveSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position)
2299     : CompositeEditCommandImpl(document), m_fragment(fragment), m_position(position)
2300 {
2301 }
2302 
2303 MoveSelectionCommandImpl::~MoveSelectionCommandImpl()
2304 {
2305 }
2306 
2307 void MoveSelectionCommandImpl::doApply()
2308 {
2309     Selection selection = endingSelection();
2310     assert(selection.state() == Selection::RANGE);
2311 
2312     // Update the position otherwise it may become invalid after the selection is deleted.
2313     NodeImpl *positionNode = m_position.node();
2314     long positionOffset = m_position.offset();
2315     Position selectionEnd = selection.end();
2316     long selectionEndOffset = selectionEnd.offset();
2317     if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) {
2318         positionOffset -= selectionEndOffset;
2319         Position selectionStart = selection.start();
2320         if (selectionStart.node() == positionNode) {
2321             positionOffset += selectionStart.offset();
2322         }
2323     }
2324 
2325     deleteSelection();
2326 
2327     setEndingSelection(Position(positionNode, positionOffset));
2328     RefPtr<ReplaceSelectionCommandImpl> cmd = new ReplaceSelectionCommandImpl(document(), m_fragment, true);
2329     applyCommandToComposite(cmd);
2330 }
2331 
2332 //------------------------------------------------------------------------------------------
2333 // RemoveCSSPropertyCommandImpl
2334 
2335 RemoveCSSPropertyCommandImpl::RemoveCSSPropertyCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property)
2336     : EditCommandImpl(document), m_decl(decl), m_property(property), m_important(false)
2337 {
2338     assert(m_decl);
2339     m_decl->ref();
2340 }
2341 
2342 RemoveCSSPropertyCommandImpl::~RemoveCSSPropertyCommandImpl()
2343 {
2344     assert(m_decl);
2345     m_decl->deref();
2346 }
2347 
2348 void RemoveCSSPropertyCommandImpl::doApply()
2349 {
2350     assert(m_decl);
2351 
2352     m_oldValue = m_decl->getPropertyValue(m_property);
2353     assert(!m_oldValue.isNull());
2354 
2355     m_important = m_decl->getPropertyPriority(m_property);
2356     m_decl->removeProperty(m_property);
2357 }
2358 
2359 void RemoveCSSPropertyCommandImpl::doUnapply()
2360 {
2361     assert(m_decl);
2362     assert(!m_oldValue.isNull());
2363 
2364     m_decl->setProperty(m_property, m_oldValue, m_important);
2365 }
2366 
2367 //------------------------------------------------------------------------------------------
2368 // RemoveNodeAttributeCommandImpl
2369 
2370 RemoveNodeAttributeCommandImpl::RemoveNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute)
2371     : EditCommandImpl(document), m_element(element), m_attribute(attribute)
2372 {
2373     assert(m_element);
2374     m_element->ref();
2375 }
2376 
2377 RemoveNodeAttributeCommandImpl::~RemoveNodeAttributeCommandImpl()
2378 {
2379     assert(m_element);
2380     m_element->deref();
2381 }
2382 
2383 void RemoveNodeAttributeCommandImpl::doApply()
2384 {
2385     assert(m_element);
2386 
2387     m_oldValue = m_element->getAttribute(m_attribute);
2388     assert(!m_oldValue.isNull());
2389 
2390     int exceptionCode = 0;
2391     m_element->removeAttribute(m_attribute, exceptionCode);
2392     assert(exceptionCode == 0);
2393 }
2394 
2395 void RemoveNodeAttributeCommandImpl::doUnapply()
2396 {
2397     assert(m_element);
2398     assert(!m_oldValue.isNull());
2399 
2400 //     int exceptionCode = 0;
2401     m_element->setAttribute(m_attribute, m_oldValue.implementation());
2402 //     assert(exceptionCode == 0);
2403 }
2404 
2405 //------------------------------------------------------------------------------------------
2406 // RemoveNodeCommandImpl
2407 
2408 RemoveNodeCommandImpl::RemoveNodeCommandImpl(DocumentImpl *document, NodeImpl *removeChild)
2409     : EditCommandImpl(document), m_parent(nullptr), m_removeChild(removeChild), m_refChild(nullptr)
2410 {
2411     assert(m_removeChild);
2412     m_removeChild->ref();
2413 
2414     m_parent = m_removeChild->parentNode();
2415     assert(m_parent);
2416     m_parent->ref();
2417 
2418     RefPtr<DOM::NodeListImpl> children = m_parent->childNodes();
2419     for (long i = children->length() - 1; i >= 0; --i) {
2420         NodeImpl *node = children->item(i);
2421         if (node == m_removeChild) {
2422             break;
2423         }
2424         m_refChild = node;
2425     }
2426 
2427     if (m_refChild) {
2428         m_refChild->ref();
2429     }
2430 }
2431 
2432 RemoveNodeCommandImpl::~RemoveNodeCommandImpl()
2433 {
2434     if (m_parent) {
2435         m_parent->deref();
2436     }
2437     if (m_removeChild) {
2438         m_removeChild->deref();
2439     }
2440     if (m_refChild) {
2441         m_refChild->deref();
2442     }
2443 }
2444 
2445 void RemoveNodeCommandImpl::doApply()
2446 {
2447     assert(m_parent);
2448     assert(m_removeChild);
2449 
2450     int exceptionCode = 0;
2451     m_parent->removeChild(m_removeChild, exceptionCode);
2452     assert(exceptionCode == 0);
2453 }
2454 
2455 void RemoveNodeCommandImpl::doUnapply()
2456 {
2457     assert(m_parent);
2458     assert(m_removeChild);
2459 
2460     int exceptionCode = 0;
2461     if (m_refChild) {
2462         m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
2463     } else {
2464         m_parent->appendChild(m_removeChild, exceptionCode);
2465     }
2466     assert(exceptionCode == 0);
2467 }
2468 
2469 //------------------------------------------------------------------------------------------
2470 // RemoveNodeAndPruneCommandImpl
2471 
2472 RemoveNodeAndPruneCommandImpl::RemoveNodeAndPruneCommandImpl(DocumentImpl *document, NodeImpl *pruneNode, NodeImpl *stopNode)
2473     : CompositeEditCommandImpl(document), m_pruneNode(pruneNode), m_stopNode(stopNode)
2474 {
2475     assert(m_pruneNode);
2476     m_pruneNode->ref();
2477     if (m_stopNode) {
2478         m_stopNode->ref();
2479     }
2480 }
2481 
2482 RemoveNodeAndPruneCommandImpl::~RemoveNodeAndPruneCommandImpl()
2483 {
2484     m_pruneNode->deref();
2485     if (m_stopNode) {
2486         m_stopNode->deref();
2487     }
2488 }
2489 
2490 void RemoveNodeAndPruneCommandImpl::doApply()
2491 {
2492     NodeImpl *editableBlock = m_pruneNode->enclosingBlockFlowElement();
2493     NodeImpl *pruneNode = m_pruneNode;
2494     NodeImpl *node = pruneNode->traversePreviousNode();
2495     removeNode(pruneNode);
2496     while (1) {
2497         if (node == m_stopNode || editableBlock != node->enclosingBlockFlowElement() || !shouldPruneNode(node)) {
2498             break;
2499         }
2500         pruneNode = node;
2501         node = node->traversePreviousNode();
2502         removeNode(pruneNode);
2503     }
2504 }
2505 
2506 //------------------------------------------------------------------------------------------
2507 // RemoveNodePreservingChildrenCommandImpl
2508 
2509 RemoveNodePreservingChildrenCommandImpl::RemoveNodePreservingChildrenCommandImpl(DocumentImpl *document, NodeImpl *node)
2510     : CompositeEditCommandImpl(document), m_node(node)
2511 {
2512     assert(m_node);
2513     m_node->ref();
2514 }
2515 
2516 RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl()
2517 {
2518     if (m_node) {
2519         m_node->deref();
2520     }
2521 }
2522 
2523 void RemoveNodePreservingChildrenCommandImpl::doApply()
2524 {
2525     RefPtr<DOM::NodeListImpl> children = node()->childNodes();
2526     const unsigned int length = children->length();
2527     for (unsigned int i = 0; i < length; ++i) {
2528         NodeImpl *child = children->item(0);
2529         removeNode(child);
2530         insertNodeBefore(child, node());
2531     }
2532     removeNode(node());
2533 }
2534 
2535 //------------------------------------------------------------------------------------------
2536 // SetNodeAttributeCommandImpl
2537 
2538 SetNodeAttributeCommandImpl::SetNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value)
2539     : EditCommandImpl(document), m_element(element), m_attribute(attribute), m_value(value)
2540 {
2541     assert(m_element);
2542     m_element->ref();
2543     assert(!m_value.isNull());
2544 }
2545 
2546 SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl()
2547 {
2548     if (m_element) {
2549         m_element->deref();
2550     }
2551 }
2552 
2553 void SetNodeAttributeCommandImpl::doApply()
2554 {
2555     assert(m_element);
2556     assert(!m_value.isNull());
2557 
2558 //     int exceptionCode = 0;
2559     m_oldValue = m_element->getAttribute(m_attribute);
2560     m_element->setAttribute(m_attribute, m_value.implementation());
2561 //     assert(exceptionCode == 0);
2562 }
2563 
2564 void SetNodeAttributeCommandImpl::doUnapply()
2565 {
2566     assert(m_element);
2567     assert(!m_oldValue.isNull());
2568 
2569 //     int exceptionCode = 0;
2570     m_element->setAttribute(m_attribute, m_oldValue.implementation());
2571 //     assert(exceptionCode == 0);
2572 }
2573 
2574 //------------------------------------------------------------------------------------------
2575 // SplitTextNodeCommandImpl
2576 
2577 SplitTextNodeCommandImpl::SplitTextNodeCommandImpl(DocumentImpl *document, TextImpl *text, long offset)
2578     : EditCommandImpl(document), m_text1(nullptr), m_text2(text), m_offset(offset)
2579 {
2580     assert(m_text2);
2581     assert(m_text2->length() > 0);
2582 
2583     m_text2->ref();
2584 }
2585 
2586 SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl()
2587 {
2588     if (m_text1) {
2589         m_text1->deref();
2590     }
2591     if (m_text2) {
2592         m_text2->deref();
2593     }
2594 }
2595 
2596 void SplitTextNodeCommandImpl::doApply()
2597 {
2598     assert(m_text2);
2599     assert(m_offset > 0);
2600 
2601     int exceptionCode = 0;
2602 
2603     // EDIT FIXME: This should use better smarts for figuring out which portion
2604     // of the split to copy (based on their comparative sizes). We should also
2605     // just use the DOM's splitText function.
2606 
2607     if (!m_text1) {
2608         // create only if needed.
2609         // if reapplying, this object will already exist.
2610         m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode));
2611         assert(exceptionCode == 0);
2612         assert(m_text1);
2613         m_text1->ref();
2614     }
2615 
2616     m_text2->deleteData(0, m_offset, exceptionCode);
2617     assert(exceptionCode == 0);
2618 
2619     m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
2620     assert(exceptionCode == 0);
2621 
2622     assert(m_text2->previousSibling()->isTextNode());
2623     assert(m_text2->previousSibling() == m_text1);
2624 }
2625 
2626 void SplitTextNodeCommandImpl::doUnapply()
2627 {
2628     assert(m_text1);
2629     assert(m_text2);
2630 
2631     assert(m_text1->nextSibling() == m_text2);
2632 
2633     int exceptionCode = 0;
2634     m_text2->insertData(0, m_text1->data(), exceptionCode);
2635     assert(exceptionCode == 0);
2636 
2637     m_text2->parentNode()->removeChild(m_text1, exceptionCode);
2638     assert(exceptionCode == 0);
2639 
2640     m_offset = m_text1->length();
2641 }
2642 
2643 //------------------------------------------------------------------------------------------
2644 // TypingCommandImpl
2645 
2646 TypingCommandImpl::TypingCommandImpl(DocumentImpl *document)
2647     : CompositeEditCommandImpl(document), m_openForMoreTyping(true)
2648 {
2649 }
2650 
2651 TypingCommandImpl::~TypingCommandImpl()
2652 {
2653 }
2654 
2655 void TypingCommandImpl::doApply()
2656 {
2657 }
2658 
2659 void TypingCommandImpl::typingAddedToOpenCommand()
2660 {
2661     assert(document());
2662     assert(document()->part());
2663     document()->part()->editor()->appliedEditing(this);
2664 }
2665 
2666 void TypingCommandImpl::insertText(const DOMString &text)
2667 {
2668     if (document()->part()->editor()->typingStyle() || m_cmds.count() == 0) {
2669         RefPtr<InputTextCommandImpl> cmd = new InputTextCommandImpl(document());
2670         applyCommandToComposite(cmd);
2671         cmd->input(text);
2672     } else {
2673         EditCommandImpl *lastCommand = m_cmds.last().get();
2674         if (lastCommand->isInputTextCommand()) {
2675             static_cast<InputTextCommandImpl *>(lastCommand)->input(text);
2676         } else {
2677             RefPtr<InputTextCommandImpl> cmd = new InputTextCommandImpl(document());
2678             applyCommandToComposite(cmd);
2679             cmd->input(text);
2680         }
2681     }
2682     typingAddedToOpenCommand();
2683 }
2684 
2685 void TypingCommandImpl::insertNewline()
2686 {
2687     RefPtr<InputNewlineCommandImpl> cmd = new InputNewlineCommandImpl(document());
2688     applyCommandToComposite(cmd);
2689     typingAddedToOpenCommand();
2690 }
2691 
2692 void TypingCommandImpl::issueCommandForDeleteKey()
2693 {
2694     Selection selectionToDelete = endingSelection();
2695     assert(selectionToDelete.state() != Selection::NONE);
2696 
2697 #ifdef DEBUG_COMMANDS
2698     qCDebug(KHTML_LOG) << "[selection]" << selectionToDelete;
2699 #endif
2700     if (selectionToDelete.state() == Selection::CARET) {
2701 #ifdef DEBUG_COMMANDS
2702         qCDebug(KHTML_LOG) << "[caret selection]";
2703 #endif
2704         Position pos(selectionToDelete.start());
2705         if (pos.inFirstEditableInRootEditableElement() && pos.offset() <= pos.node()->caretMinOffset()) {
2706             // we're at the start of a root editable block...do nothing
2707             return;
2708         }
2709         selectionToDelete = Selection(pos.previousCharacterPosition(), pos);
2710 #ifdef DEBUG_COMMANDS
2711         qCDebug(KHTML_LOG) << "[modified selection]" << selectionToDelete;
2712 #endif
2713     }
2714     deleteSelection(selectionToDelete);
2715     typingAddedToOpenCommand();
2716 }
2717 
2718 void TypingCommandImpl::deleteKeyPressed()
2719 {
2720 // EDIT FIXME: The ifdef'ed out code below should be re-enabled.
2721 // In order for this to happen, the deleteCharacter case
2722 // needs work. Specifically, the caret-positioning code
2723 // and whitespace-handling code in DeleteSelectionCommandImpl::doApply()
2724 // needs to be factored out so it can be used again here.
2725 // Until that work is done, issueCommandForDeleteKey() does the
2726 // right thing, but less efficiently and with the cost of more
2727 // objects.
2728     issueCommandForDeleteKey();
2729 #if 0
2730     if (m_cmds.count() == 0) {
2731         issueCommandForDeleteKey();
2732     } else {
2733         EditCommand lastCommand = m_cmds.last();
2734         if (lastCommand.commandID() == InputTextCommandID) {
2735             InputTextCommand cmd = static_cast<InputTextCommand &>(lastCommand);
2736             cmd.deleteCharacter();
2737             if (cmd.charactersAdded() == 0) {
2738                 removeCommand(cmd);
2739             }
2740         } else if (lastCommand.commandID() == InputNewlineCommandID) {
2741             lastCommand.unapply();
2742             removeCommand(lastCommand);
2743         } else {
2744             issueCommandForDeleteKey();
2745         }
2746     }
2747 #endif
2748 }
2749 
2750 void TypingCommandImpl::removeCommand(const PassRefPtr<EditCommandImpl> cmd)
2751 {
2752     // NOTE: If the passed-in command is the last command in the
2753     // composite, we could remove all traces of this typing command
2754     // from the system, including the undo chain. Other editors do
2755     // not do this, but we could.
2756 
2757     m_cmds.removeAll(cmd);
2758     if (m_cmds.count() == 0) {
2759         setEndingSelection(startingSelection());
2760     } else {
2761         setEndingSelection(m_cmds.last()->endingSelection());
2762     }
2763 }
2764 
2765 static bool isOpenForMoreTypingCommand(const EditCommandImpl *command)
2766 {
2767     return command && command->isTypingCommand() &&
2768            static_cast<const TypingCommandImpl *>(command)->openForMoreTyping();
2769 }
2770 
2771 void TypingCommandImpl::deleteKeyPressed0(DocumentImpl *document)
2772 {
2773     //Editor *editor = document->part()->editor();
2774     // FIXME reenable after properly modify selection of the lastEditCommand
2775     // if (isOpenForMoreTypingCommand(lastEditCommand)) {
2776     //     static_cast<TypingCommand &>(lastEditCommand).deleteKeyPressed();
2777     // } else {
2778     RefPtr<TypingCommandImpl> command = new TypingCommandImpl(document);
2779     command->apply();
2780     command->deleteKeyPressed();
2781     // }
2782 }
2783 
2784 void TypingCommandImpl::insertNewline0(DocumentImpl *document)
2785 {
2786     assert(document);
2787     Editor *ed = document->part()->editor();
2788     assert(ed);
2789     EditCommandImpl *lastEditCommand = ed->lastEditCommand().get();
2790     if (isOpenForMoreTypingCommand(lastEditCommand)) {
2791         static_cast<TypingCommandImpl *>(lastEditCommand)->insertNewline();
2792     } else {
2793         RefPtr<TypingCommandImpl> command = new TypingCommandImpl(document);
2794         command->apply();
2795         command->insertNewline();
2796     }
2797 }
2798 
2799 void TypingCommandImpl::insertText0(DocumentImpl *document, const DOMString &text)
2800 {
2801 #ifdef DEBUG_COMMANDS
2802     qCDebug(KHTML_LOG) << "[insert text]" << text;
2803 #endif
2804     assert(document);
2805     Editor *ed = document->part()->editor();
2806     assert(ed);
2807     EditCommandImpl *lastEditCommand = ed->lastEditCommand().get();
2808     if (isOpenForMoreTypingCommand(lastEditCommand)) {
2809         static_cast<TypingCommandImpl *>(lastEditCommand)->insertText(text);
2810     } else {
2811         RefPtr<TypingCommandImpl> command = new TypingCommandImpl(document);
2812         command->apply();
2813         command->insertText(text);
2814     }
2815 }
2816 
2817 //------------------------------------------------------------------------------------------
2818 // InsertListCommandImpl
2819 
2820 InsertListCommandImpl::InsertListCommandImpl(DocumentImpl *document, Type type)
2821     : CompositeEditCommandImpl(document), m_listType(type)
2822 {
2823 }
2824 
2825 InsertListCommandImpl::~InsertListCommandImpl()
2826 {
2827 }
2828 
2829 void InsertListCommandImpl::doApply()
2830 {
2831 #ifdef DEBUG_COMMANDS
2832     qCDebug(KHTML_LOG) << "[make current selection/paragraph a list]" << endingSelection();
2833 #endif
2834     Position start = endingSelection().start();
2835     Position end = endingSelection().end();
2836     ElementImpl *startBlock = start.node()->enclosingBlockFlowElement();
2837     ElementImpl *endBlock = end.node()->enclosingBlockFlowElement();
2838 #ifdef DEBUG_COMMANDS
2839     qCDebug(KHTML_LOG) << "[start:end blocks]" << startBlock << endBlock;
2840     printEnclosingBlockTree(start.node());
2841 #endif
2842     if (startBlock == endBlock) {
2843         if (startBlock->id() == ID_LI) {
2844             // we already have a list item, remove it then
2845 #ifdef DEBUG_COMMANDS
2846             qCDebug(KHTML_LOG) << "[remove list item]";
2847 #endif
2848             NodeImpl *listBlock = startBlock->parent(); // it's either <ol> or <ul>
2849             // we need to properly split or even remove the list leaving 2 lists:
2850             // [listBlock->firstChild(), startBlock) and (startBlock, listBlock->lastChild()]
2851             if (listBlock->firstChild() == listBlock->lastChild() && listBlock->firstChild() == startBlock) {
2852                 // get rid of list completely
2853 #ifdef DEBUG_COMMANDS
2854                 qCDebug(KHTML_LOG) << "[remove list completely]";
2855 #endif
2856                 removeNodePreservingChildren(listBlock);
2857                 removeNodePreservingChildren(startBlock);
2858             } else if (!startBlock->previousSibling()) {
2859                 // move nodes from this list item before the list
2860                 NodeImpl *nextSibling;
2861                 for (NodeImpl *node = startBlock->firstChild(); node; node = nextSibling) {
2862                     nextSibling = node->nextSibling();
2863                     removeNode(node);
2864                     insertNodeBefore(node, listBlock);
2865                 }
2866                 removeNode(startBlock);
2867             } else if (!startBlock->nextSibling()) {
2868                 // move nodes from this list item after the list
2869                 NodeImpl *nextSibling;
2870                 for (NodeImpl *node = startBlock->lastChild(); node; node = nextSibling) {
2871                     nextSibling = node->previousSibling();
2872                     removeNode(node);
2873                     insertNodeAfter(node, listBlock);
2874                 }
2875                 removeNode(startBlock);
2876             } else {
2877                 // split list into 2 and nodes from this list item goes between lists
2878                 WTF::PassRefPtr<NodeImpl> newListBlock = listBlock->cloneNode(false);
2879                 insertNodeAfter(newListBlock.get(), listBlock);
2880                 NodeImpl *node, *nextSibling;
2881                 for (node = startBlock->nextSibling(); node; node = nextSibling) {
2882                     nextSibling = node->nextSibling();
2883                     removeNode(node);
2884                     appendNode(newListBlock.get(), node);
2885                 }
2886                 for (node = startBlock->firstChild(); node; node = nextSibling) {
2887                     nextSibling = node->nextSibling();
2888                     removeNode(node);
2889                     insertNodeBefore(node, newListBlock.get());
2890                 }
2891                 removeNode(startBlock);
2892             }
2893         } else {
2894             ElementImpl *ol = document()->createHTMLElement(m_listType == OrderedList ? "OL" : "UL");
2895             ElementImpl *li = document()->createHTMLElement("LI");
2896             appendNode(ol, li);
2897             NodeImpl *nextNode;
2898             for (NodeImpl *node = startBlock->firstChild(); node; node = nextNode) {
2899 #ifdef DEBUG_COMMANDS
2900                 qCDebug(KHTML_LOG) << "[reattach node]" << node;
2901 #endif
2902                 nextNode = node->nextSibling();
2903                 removeNode(node);
2904                 appendNode(li, node);
2905             }
2906             appendNode(startBlock, ol);
2907         }
2908     } else {
2909 #ifdef DEBUG_COMMANDS
2910         qCDebug(KHTML_LOG) << "[different blocks are not supported yet]";
2911 #endif
2912     }
2913 }
2914 
2915 void InsertListCommandImpl::insertList(DocumentImpl *document, Type type)
2916 {
2917     RefPtr<InsertListCommandImpl> insertCommand = new InsertListCommandImpl(document, type);
2918     insertCommand->apply();
2919 }
2920 
2921 //------------------------------------------------------------------------------------------
2922 
2923 //------------------------------------------------------------------------------------------
2924 // IndentOutdentCommandImpl
2925 
2926 IndentOutdentCommandImpl::IndentOutdentCommandImpl(DocumentImpl *document, Type type)
2927     : CompositeEditCommandImpl(document), m_commandType(type)
2928 {
2929 }
2930 
2931 IndentOutdentCommandImpl::~IndentOutdentCommandImpl()
2932 {
2933 }
2934 
2935 void IndentOutdentCommandImpl::indent()
2936 {
2937     Selection selection = endingSelection();
2938 #ifdef DEBUG_COMMANDS
2939     qCDebug(KHTML_LOG) << "[indent selection]" << selection;
2940 #endif
2941     NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement();
2942     NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement();
2943 
2944     if (startBlock == endBlock) {
2945         // check if selection is the list, but not fully covered
2946         if (startBlock->id() == ID_LI && (startBlock->previousSibling() || startBlock->nextSibling())) {
2947 #ifdef DEBUG_COMMANDS
2948             qCDebug(KHTML_LOG) << "[modify list]";
2949 #endif
2950             RefPtr<NodeImpl> newList = startBlock->parent()->cloneNode(false);
2951             insertNodeAfter(newList.get(), startBlock);
2952             removeNode(startBlock);
2953             appendNode(newList.get(), startBlock);
2954         } else {
2955             NodeImpl *blockquoteElement = document()->createHTMLElement("blockquote");
2956             if (startBlock->id() == ID_LI) {
2957                 startBlock = startBlock->parent();
2958                 NodeImpl *parent = startBlock->parent();
2959                 removeNode(startBlock);
2960                 appendNode(parent, blockquoteElement);
2961                 appendNode(blockquoteElement, startBlock);
2962             } else {
2963                 NodeImpl *parent = startBlock->parent();
2964                 removeNode(startBlock);
2965                 appendNode(parent, blockquoteElement);
2966                 appendNode(blockquoteElement, startBlock);
2967             }
2968         }
2969     } else {
2970         if (startBlock->id() == ID_LI && endBlock->id() == ID_LI && startBlock->parent() == endBlock->parent()) {
2971 #ifdef DEBUG_COMMANDS
2972             qCDebug(KHTML_LOG) << "[indent some items inside list]";
2973 #endif
2974             RefPtr<NodeImpl> nestedList = startBlock->parent()->cloneNode(false);
2975             insertNodeBefore(nestedList.get(), startBlock);
2976             NodeImpl *nextNode = nullptr;
2977             for (NodeImpl *node = startBlock;; node = nextNode) {
2978                 nextNode = node->nextSibling();
2979                 removeNode(node);
2980                 appendNode(nestedList.get(), node);
2981                 if (node == endBlock) {
2982                     break;
2983                 }
2984             }
2985         } else {
2986 #ifdef DEBUG_COMMANDS
2987             qCDebug(KHTML_LOG) << "[blocks not from one list are not supported yet]";
2988 #endif
2989         }
2990     }
2991 }
2992 
2993 static bool hasPreviousListItem(NodeImpl *node)
2994 {
2995     while (node) {
2996         node = node->previousSibling();
2997         if (node && node->id() == ID_LI) {
2998             return true;
2999         }
3000     }
3001     return false;
3002 }
3003 
3004 static bool hasNextListItem(NodeImpl *node)
3005 {
3006     while (node) {
3007         node = node->nextSibling();
3008         if (node && node->id() == ID_LI) {
3009             return true;
3010         }
3011     }
3012     return false;
3013 }
3014 
3015 void IndentOutdentCommandImpl::outdent()
3016 {
3017     Selection selection = endingSelection();
3018 #ifdef DEBUG_COMMANDS
3019     qCDebug(KHTML_LOG) << "[indent selection]" << selection;
3020 #endif
3021     NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement();
3022     NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement();
3023 
3024     if (startBlock->id() == ID_LI && endBlock->id() == ID_LI && startBlock->parent() == endBlock->parent()) {
3025 #ifdef DEBUG_COMMANDS
3026         qCDebug(KHTML_LOG) << "[list items selected]";
3027 #endif
3028         bool firstItemSelected = !hasPreviousListItem(startBlock);
3029         bool lastItemSelected = !hasNextListItem(endBlock);
3030         bool listFullySelected = firstItemSelected && lastItemSelected;
3031 
3032 #ifdef DEBUG_COMMANDS
3033         qCDebug(KHTML_LOG) << "[first/last item selected]" << firstItemSelected << lastItemSelected;
3034 #endif
3035 
3036         NodeImpl *listNode = startBlock->parent();
3037         printEnclosingBlockTree(listNode);
3038         bool hasParentList = listNode->parent()->id() == ID_OL || listNode->parent()->id() == ID_UL;
3039 
3040         if (!firstItemSelected && !lastItemSelected) {
3041             // split the list into 2 and reattach all the nodes before the first selected item to the second list
3042             RefPtr<NodeImpl> clonedList = listNode->cloneNode(false);
3043             NodeImpl *nextNode = nullptr;
3044             for (NodeImpl *node = listNode->firstChild(); node != startBlock; node = nextNode) {
3045                 nextNode = node->nextSibling();
3046                 removeNode(node);
3047                 appendNode(clonedList.get(), node);
3048             }
3049             insertNodeBefore(clonedList.get(), listNode);
3050             // so now the first item selected
3051             firstItemSelected = true;
3052         }
3053 
3054         NodeImpl *nextNode = nullptr;
3055         for (NodeImpl *node = firstItemSelected ? startBlock : endBlock;; node = nextNode) {
3056             nextNode = firstItemSelected ? node->nextSibling() : node->previousSibling();
3057             removeNode(node);
3058             if (firstItemSelected) {
3059                 insertNodeBefore(node, listNode);
3060             } else {
3061                 insertNodeAfter(node, listNode);
3062             }
3063             if (!hasParentList && node->id() == ID_LI) {
3064                 insertNodeAfter(document()->createHTMLElement("BR"), node);
3065                 removeNodePreservingChildren(node);
3066             }
3067             if (node == (firstItemSelected ? endBlock : startBlock)) {
3068                 break;
3069             }
3070         }
3071         if (listFullySelected) {
3072             removeNode(listNode);
3073         }
3074         return;
3075     }
3076 
3077     if (startBlock == endBlock) {
3078         if (startBlock->id() == ID_BLOCKQUOTE) {
3079             removeNodePreservingChildren(startBlock);
3080         } else {
3081 #ifdef DEBUG_COMMANDS
3082             qCDebug(KHTML_LOG) << "[not the list or blockquote]";
3083 #endif
3084         }
3085     } else {
3086 #ifdef DEBUG_COMMANDS
3087         qCDebug(KHTML_LOG) << "[blocks not from one list are not supported yet]";
3088 #endif
3089     }
3090 }
3091 
3092 void IndentOutdentCommandImpl::doApply()
3093 {
3094     if (m_commandType == Indent) {
3095         indent();
3096     } else {
3097         outdent();
3098     }
3099 }
3100 
3101 //------------------------------------------------------------------------------------------
3102 
3103 } // namespace khtml
3104