File indexing completed on 2024-04-28 11:38:09
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