KHTML
htmlediting_impl.cpp
Go to the documentation of this file.
00001 /* 00002 * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. 00003 * 00004 * Redistribution and use in source and binary forms, with or without 00005 * modification, are permitted provided that the following conditions 00006 * are met: 00007 * 1. Redistributions of source code must retain the above copyright 00008 * notice, this list of conditions and the following disclaimer. 00009 * 2. Redistributions in binary form must reproduce the above copyright 00010 * notice, this list of conditions and the following disclaimer in the 00011 * documentation and/or other materials provided with the distribution. 00012 * 00013 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 00014 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00015 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 00016 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 00017 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 00018 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 00019 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 00020 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 00021 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 00022 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 00023 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00024 */ 00025 00026 #include "htmlediting_impl.h" 00027 #include "editor.h" 00028 00029 #include "css/cssproperties.h" 00030 #include "css/css_valueimpl.h" 00031 #include "dom/css_value.h" 00032 #include "html/html_elementimpl.h" 00033 #include "html/html_imageimpl.h" 00034 #include "rendering/render_object.h" 00035 #include "rendering/render_style.h" 00036 #include "rendering/render_text.h" 00037 #include "xml/dom_docimpl.h" 00038 #include "xml/dom_elementimpl.h" 00039 #include "xml/dom_position.h" 00040 #include "xml/dom_positioniterator.h" 00041 #include "xml/dom_nodeimpl.h" 00042 #include "xml/dom_selection.h" 00043 #include "xml/dom_stringimpl.h" 00044 #include "xml/dom_textimpl.h" 00045 #include "xml/dom2_rangeimpl.h" 00046 #include "xml/dom2_viewsimpl.h" 00047 00048 #include "khtml_part.h" 00049 #include "khtmlview.h" 00050 00051 #include <QList> 00052 #include <limits.h> 00053 00054 using DOM::AttrImpl; 00055 using DOM::CSSPrimitiveValue; 00056 using DOM::CSSPrimitiveValueImpl; 00057 using DOM::CSSProperty; 00058 using DOM::CSSStyleDeclarationImpl; 00059 using DOM::CSSValueImpl; 00060 using DOM::DocumentFragmentImpl; 00061 using DOM::DocumentImpl; 00062 using DOM::DOMString; 00063 using DOM::DOMStringImpl; 00064 using DOM::EditingTextImpl; 00065 using DOM::PositionIterator; 00066 using DOM::ElementImpl; 00067 using DOM::HTMLElementImpl; 00068 using DOM::HTMLImageElementImpl; 00069 using DOM::NamedAttrMapImpl; 00070 using DOM::Node; 00071 using DOM::NodeImpl; 00072 using DOM::NodeListImpl; 00073 using DOM::Position; 00074 using DOM::Range; 00075 using DOM::RangeImpl; 00076 using DOM::Selection; 00077 using DOM::TextImpl; 00078 using DOM::TreeWalkerImpl; 00079 using DOM::Editor; 00080 00081 #define DEBUG_COMMANDS 1 00082 00083 namespace khtml { 00084 00085 00086 static inline bool isNBSP(const QChar &c) 00087 { 00088 return c == QChar(0xa0); 00089 } 00090 00091 static inline bool isWS(const QChar &c) 00092 { 00093 return c.isSpace() && c != QChar(0xa0); 00094 } 00095 00096 static inline bool isWS(const DOMString &text) 00097 { 00098 if (text.length() != 1) 00099 return false; 00100 00101 return isWS(text[0]); 00102 } 00103 00104 static inline bool isWS(const Position &pos) 00105 { 00106 if (!pos.node()) 00107 return false; 00108 00109 if (!pos.node()->isTextNode()) 00110 return false; 00111 00112 const DOMString &string = static_cast<TextImpl *>(pos.node())->data(); 00113 return isWS(string[pos.offset()]); 00114 } 00115 00116 static bool shouldPruneNode(NodeImpl *node) 00117 { 00118 if (!node) 00119 return false; 00120 00121 RenderObject *renderer = node->renderer(); 00122 if (!renderer) 00123 return true; 00124 00125 if (node->hasChildNodes()) 00126 return false; 00127 00128 if (node->rootEditableElement() == node) 00129 return false; 00130 00131 if (renderer->isBR() || renderer->isReplaced()) 00132 return false; 00133 00134 if (node->isTextNode()) { 00135 TextImpl *text = static_cast<TextImpl *>(node); 00136 if (text->length() == 0) 00137 return true; 00138 return false; 00139 } 00140 00141 if (!node->isHTMLElement()/* && !node->isXMLElementNode()*/) 00142 return false; 00143 00144 if (node->id() == ID_BODY) 00145 return false; 00146 00147 if (!node->isContentEditable()) 00148 return false; 00149 00150 return true; 00151 } 00152 00153 static Position leadingWhitespacePosition(const Position &pos) 00154 { 00155 assert(pos.notEmpty()); 00156 00157 Selection selection(pos); 00158 Position prev = pos.previousCharacterPosition(); 00159 if (prev != pos && prev.node()->inSameContainingBlockFlowElement(pos.node()) && prev.node()->isTextNode()) { 00160 DOMString string = static_cast<TextImpl *>(prev.node())->data(); 00161 if (isWS(string[prev.offset()])) 00162 return prev; 00163 } 00164 00165 return Position(); 00166 } 00167 00168 static Position trailingWhitespacePosition(const Position &pos) 00169 { 00170 assert(pos.notEmpty()); 00171 00172 if (pos.node()->isTextNode()) { 00173 TextImpl *textNode = static_cast<TextImpl *>(pos.node()); 00174 if (pos.offset() >= (long)textNode->length()) { 00175 Position next = pos.nextCharacterPosition(); 00176 if (next != pos && next.node()->inSameContainingBlockFlowElement(pos.node()) && next.node()->isTextNode()) { 00177 DOMString string = static_cast<TextImpl *>(next.node())->data(); 00178 if (isWS(string[0])) 00179 return next; 00180 } 00181 } 00182 else { 00183 DOMString string = static_cast<TextImpl *>(pos.node())->data(); 00184 if (isWS(string[pos.offset()])) 00185 return pos; 00186 } 00187 } 00188 00189 return Position(); 00190 } 00191 00192 static bool textNodesAreJoinable(TextImpl *text1, TextImpl *text2) 00193 { 00194 assert(text1); 00195 assert(text2); 00196 00197 return (text1->nextSibling() == text2); 00198 } 00199 00200 static DOMString &nonBreakingSpaceString() 00201 { 00202 static DOMString nonBreakingSpaceString = QString(QChar(0xa0)); 00203 return nonBreakingSpaceString; 00204 } 00205 00206 static DOMString &styleSpanClassString() 00207 { 00208 static DOMString styleSpanClassString = "khtml-style-span"; 00209 return styleSpanClassString; 00210 } 00211 00212 //------------------------------------------------------------------------------------------ 00213 // EditCommandImpl 00214 00215 EditCommandImpl::EditCommandImpl(DocumentImpl *document) 00216 : SharedCommandImpl(), m_document(document), m_state(NotApplied), m_parent(0) 00217 { 00218 assert(m_document); 00219 assert(m_document->part()); 00220 m_document->ref(); 00221 m_startingSelection = m_document->part()->caret(); 00222 m_endingSelection = m_startingSelection; 00223 } 00224 00225 EditCommandImpl::~EditCommandImpl() 00226 { 00227 m_document->deref(); 00228 } 00229 00230 void EditCommandImpl::apply() 00231 { 00232 assert(m_document); 00233 assert(m_document->part()); 00234 assert(state() == NotApplied); 00235 00236 doApply(); 00237 00238 m_state = Applied; 00239 00240 if (!isCompositeStep()) 00241 m_document->part()->editor()->appliedEditing(this); 00242 } 00243 00244 void EditCommandImpl::unapply() 00245 { 00246 assert(m_document); 00247 assert(m_document->part()); 00248 assert(state() == Applied); 00249 00250 doUnapply(); 00251 00252 m_state = NotApplied; 00253 00254 if (!isCompositeStep()) 00255 m_document->part()->editor()->unappliedEditing(this); 00256 } 00257 00258 void EditCommandImpl::reapply() 00259 { 00260 assert(m_document); 00261 assert(m_document->part()); 00262 assert(state() == NotApplied); 00263 00264 doReapply(); 00265 00266 m_state = Applied; 00267 00268 if (!isCompositeStep()) 00269 m_document->part()->editor()->reappliedEditing(this); 00270 } 00271 00272 void EditCommandImpl::doReapply() 00273 { 00274 doApply(); 00275 } 00276 00277 void EditCommandImpl::setStartingSelection(const Selection &s) 00278 { 00279 m_startingSelection = s; 00280 EditCommandImpl *cmd = parent(); 00281 while (cmd) { 00282 cmd->m_startingSelection = s; 00283 cmd = cmd->parent(); 00284 } 00285 } 00286 00287 void EditCommandImpl::setEndingSelection(const Selection &s) 00288 { 00289 m_endingSelection = s; 00290 EditCommandImpl *cmd = parent(); 00291 while (cmd) { 00292 cmd->m_endingSelection = s; 00293 cmd = cmd->parent(); 00294 } 00295 } 00296 00297 EditCommandImpl* EditCommandImpl::parent() const 00298 { 00299 return m_parent.get(); 00300 } 00301 00302 void EditCommandImpl::setParent(EditCommandImpl* cmd) 00303 { 00304 m_parent = cmd; 00305 } 00306 00307 //------------------------------------------------------------------------------------------ 00308 // CompositeEditCommandImpl 00309 00310 CompositeEditCommandImpl::CompositeEditCommandImpl(DocumentImpl *document) 00311 : EditCommandImpl(document) 00312 { 00313 } 00314 00315 CompositeEditCommandImpl::~CompositeEditCommandImpl() 00316 { 00317 } 00318 00319 void CompositeEditCommandImpl::doUnapply() 00320 { 00321 if (m_cmds.count() == 0) { 00322 return; 00323 } 00324 00325 for (int i = m_cmds.count() - 1; i >= 0; --i) 00326 m_cmds[i]->unapply(); 00327 00328 setState(NotApplied); 00329 } 00330 00331 void CompositeEditCommandImpl::doReapply() 00332 { 00333 if (m_cmds.count() == 0) { 00334 return; 00335 } 00336 QMutableListIterator<RefPtr<EditCommandImpl> > it(m_cmds); 00337 while (it.hasNext()) 00338 it.next()->reapply(); 00339 00340 setState(Applied); 00341 } 00342 00343 // 00344 // sugary-sweet convenience functions to help create and apply edit commands in composite commands 00345 // 00346 void CompositeEditCommandImpl::applyCommandToComposite(PassRefPtr<EditCommandImpl> cmd) 00347 { 00348 cmd->setStartingSelection(endingSelection());//###? 00349 cmd->setEndingSelection(endingSelection()); 00350 cmd->setParent(this); 00351 cmd->apply(); 00352 m_cmds.append(cmd); 00353 } 00354 00355 void CompositeEditCommandImpl::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild) 00356 { 00357 RefPtr<InsertNodeBeforeCommandImpl> cmd = new InsertNodeBeforeCommandImpl(document(), insertChild, refChild); 00358 applyCommandToComposite(cmd); 00359 } 00360 00361 void CompositeEditCommandImpl::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild) 00362 { 00363 if (refChild->parentNode()->lastChild() == refChild) { 00364 appendNode(refChild->parentNode(), insertChild); 00365 } 00366 else { 00367 assert(refChild->nextSibling()); 00368 insertNodeBefore(insertChild, refChild->nextSibling()); 00369 } 00370 } 00371 00372 void CompositeEditCommandImpl::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset) 00373 { 00374 if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) { 00375 NodeImpl *child = refChild->firstChild(); 00376 for (long i = 0; child && i < offset; i++) 00377 child = child->nextSibling(); 00378 if (child) 00379 insertNodeBefore(insertChild, child); 00380 else 00381 appendNode(refChild, insertChild); 00382 } 00383 else if (refChild->caretMinOffset() >= offset) { 00384 insertNodeBefore(insertChild, refChild); 00385 } 00386 else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) { 00387 splitTextNode(static_cast<TextImpl *>(refChild), offset); 00388 insertNodeBefore(insertChild, refChild); 00389 } 00390 else { 00391 insertNodeAfter(insertChild, refChild); 00392 } 00393 } 00394 00395 void CompositeEditCommandImpl::appendNode(NodeImpl *parent, NodeImpl *appendChild) 00396 { 00397 RefPtr<AppendNodeCommandImpl> cmd = new AppendNodeCommandImpl(document(), parent, appendChild); 00398 applyCommandToComposite(cmd); 00399 } 00400 00401 void CompositeEditCommandImpl::removeNode(NodeImpl *removeChild) 00402 { 00403 RefPtr<RemoveNodeCommandImpl> cmd = new RemoveNodeCommandImpl(document(), removeChild); 00404 applyCommandToComposite(cmd); 00405 } 00406 00407 void CompositeEditCommandImpl::removeNodeAndPrune(NodeImpl *pruneNode, NodeImpl *stopNode) 00408 { 00409 RefPtr<RemoveNodeAndPruneCommandImpl> cmd = new RemoveNodeAndPruneCommandImpl(document(), pruneNode, stopNode); 00410 applyCommandToComposite(cmd); 00411 } 00412 00413 void CompositeEditCommandImpl::removeNodePreservingChildren(NodeImpl *removeChild) 00414 { 00415 RefPtr<RemoveNodePreservingChildrenCommandImpl> cmd = new RemoveNodePreservingChildrenCommandImpl(document(), removeChild); 00416 applyCommandToComposite(cmd); 00417 } 00418 00419 void CompositeEditCommandImpl::splitTextNode(TextImpl *text, long offset) 00420 { 00421 RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, offset); 00422 applyCommandToComposite(cmd); 00423 } 00424 00425 void CompositeEditCommandImpl::joinTextNodes(TextImpl *text1, TextImpl *text2) 00426 { 00427 RefPtr<JoinTextNodesCommandImpl> cmd = new JoinTextNodesCommandImpl(document(), text1, text2); 00428 applyCommandToComposite(cmd); 00429 } 00430 00431 void CompositeEditCommandImpl::inputText(const DOMString &text) 00432 { 00433 RefPtr<InputTextCommandImpl> cmd = new InputTextCommandImpl(document()); 00434 applyCommandToComposite(cmd); 00435 cmd->input(text); 00436 } 00437 00438 void CompositeEditCommandImpl::insertText(TextImpl *node, long offset, const DOMString &text) 00439 { 00440 RefPtr<InsertTextCommandImpl> cmd = new InsertTextCommandImpl(document(), node, offset, text); 00441 applyCommandToComposite(cmd); 00442 } 00443 00444 void CompositeEditCommandImpl::deleteText(TextImpl *node, long offset, long count) 00445 { 00446 RefPtr<DeleteTextCommandImpl> cmd = new DeleteTextCommandImpl(document(), node, offset, count); 00447 applyCommandToComposite(cmd); 00448 } 00449 00450 void CompositeEditCommandImpl::replaceText(TextImpl *node, long offset, long count, const DOMString &replacementText) 00451 { 00452 RefPtr<DeleteTextCommandImpl> deleteCommand = new DeleteTextCommandImpl(document(), node, offset, count); 00453 applyCommandToComposite(deleteCommand); 00454 RefPtr<InsertTextCommandImpl> insertCommand = new InsertTextCommandImpl(document(), node, offset, replacementText); 00455 applyCommandToComposite(insertCommand); 00456 } 00457 00458 void CompositeEditCommandImpl::deleteSelection() 00459 { 00460 if (endingSelection().state() == Selection::RANGE) { 00461 RefPtr<DeleteSelectionCommandImpl> cmd = new DeleteSelectionCommandImpl(document()); 00462 applyCommandToComposite(cmd); 00463 } 00464 } 00465 00466 void CompositeEditCommandImpl::deleteSelection(const Selection &selection) 00467 { 00468 if (selection.state() == Selection::RANGE) { 00469 RefPtr<DeleteSelectionCommandImpl> cmd = new DeleteSelectionCommandImpl(document(), selection); 00470 applyCommandToComposite(cmd); 00471 } 00472 } 00473 00474 void CompositeEditCommandImpl::deleteCollapsibleWhitespace() 00475 { 00476 RefPtr<DeleteCollapsibleWhitespaceCommandImpl> cmd = new DeleteCollapsibleWhitespaceCommandImpl(document()); 00477 applyCommandToComposite(cmd); 00478 } 00479 00480 void CompositeEditCommandImpl::deleteCollapsibleWhitespace(const Selection &selection) 00481 { 00482 RefPtr<DeleteCollapsibleWhitespaceCommandImpl> cmd = new DeleteCollapsibleWhitespaceCommandImpl(document(), selection); 00483 applyCommandToComposite(cmd); 00484 } 00485 00486 void CompositeEditCommandImpl::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property) 00487 { 00488 RefPtr<RemoveCSSPropertyCommandImpl> cmd = new RemoveCSSPropertyCommandImpl(document(), decl, property); 00489 applyCommandToComposite(cmd); 00490 } 00491 00492 void CompositeEditCommandImpl::removeNodeAttribute(ElementImpl *element, int attribute) 00493 { 00494 RefPtr<RemoveNodeAttributeCommandImpl> cmd = new RemoveNodeAttributeCommandImpl(document(), element, attribute); 00495 applyCommandToComposite(cmd); 00496 } 00497 00498 void CompositeEditCommandImpl::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value) 00499 { 00500 RefPtr<SetNodeAttributeCommandImpl> cmd = new SetNodeAttributeCommandImpl(document(), element, attribute, value); 00501 applyCommandToComposite(cmd); 00502 } 00503 00504 ElementImpl *CompositeEditCommandImpl::createTypingStyleElement() const 00505 { 00506 int exceptionCode = 0; 00507 ElementImpl *styleElement = document()->createHTMLElement("SPAN"); 00508 // assert(exceptionCode == 0); 00509 00510 styleElement->setAttribute(ATTR_STYLE, document()->part()->editor()->typingStyle()->cssText().implementation()); 00511 // assert(exceptionCode == 0); 00512 00513 styleElement->setAttribute(ATTR_CLASS, styleSpanClassString()); 00514 assert(exceptionCode == 0); 00515 00516 return styleElement; 00517 } 00518 00519 //========================================================================================== 00520 // Concrete commands 00521 //------------------------------------------------------------------------------------------ 00522 // AppendNodeCommandImpl 00523 00524 AppendNodeCommandImpl::AppendNodeCommandImpl(DocumentImpl *document, NodeImpl *parentNode, NodeImpl *appendChild) 00525 : EditCommandImpl(document), m_parentNode(parentNode), m_appendChild(appendChild) 00526 { 00527 assert(m_parentNode); 00528 m_parentNode->ref(); 00529 00530 assert(m_appendChild); 00531 m_appendChild->ref(); 00532 } 00533 00534 AppendNodeCommandImpl::~AppendNodeCommandImpl() 00535 { 00536 if (m_parentNode) 00537 m_parentNode->deref(); 00538 if (m_appendChild) 00539 m_appendChild->deref(); 00540 } 00541 00542 void AppendNodeCommandImpl::doApply() 00543 { 00544 assert(m_parentNode); 00545 assert(m_appendChild); 00546 00547 int exceptionCode = 0; 00548 m_parentNode->appendChild(m_appendChild, exceptionCode); 00549 assert(exceptionCode == 0); 00550 } 00551 00552 void AppendNodeCommandImpl::doUnapply() 00553 { 00554 assert(m_parentNode); 00555 assert(m_appendChild); 00556 assert(state() == Applied); 00557 00558 int exceptionCode = 0; 00559 m_parentNode->removeChild(m_appendChild, exceptionCode); 00560 assert(exceptionCode == 0); 00561 } 00562 00563 //------------------------------------------------------------------------------------------ 00564 // ApplyStyleCommandImpl 00565 00566 ApplyStyleCommandImpl::ApplyStyleCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *style) 00567 : CompositeEditCommandImpl(document), m_style(style) 00568 { 00569 assert(m_style); 00570 m_style->ref(); 00571 } 00572 00573 ApplyStyleCommandImpl::~ApplyStyleCommandImpl() 00574 { 00575 assert(m_style); 00576 m_style->deref(); 00577 } 00578 00579 static bool isBlockLevelStyle(const CSSStyleDeclarationImpl* style) 00580 { 00581 QListIterator<CSSProperty*> it(*(style->values())); 00582 while (it.hasNext()) { 00583 CSSProperty *property = it.next(); 00584 switch (property->id()) { 00585 case CSS_PROP_TEXT_ALIGN: 00586 return true; 00587 /*case CSS_PROP_FONT_WEIGHT: 00588 if (strcasecmp(property->value()->cssText(), "bold") == 0) 00589 styleChange.applyBold = true; 00590 else 00591 styleChange.cssStyle += property->cssText(); 00592 break; 00593 case CSS_PROP_FONT_STYLE: { 00594 DOMString cssText(property->value()->cssText()); 00595 if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0) 00596 styleChange.applyItalic = true; 00597 else 00598 styleChange.cssStyle += property->cssText(); 00599 } 00600 break; 00601 default: 00602 styleChange.cssStyle += property->cssText(); 00603 break;*/ 00604 } 00605 } 00606 return false; 00607 } 00608 00609 static void applyStyleChangeOnTheNode(ElementImpl* element, CSSStyleDeclarationImpl* style) 00610 { 00611 CSSStyleDeclarationImpl *computedStyle = element->document()->defaultView()->getComputedStyle(element, 0); 00612 assert(computedStyle); 00613 #ifdef DEBUG_COMMANDS 00614 kDebug() << "[change style]" << element << endl; 00615 #endif 00616 00617 QListIterator<CSSProperty*> it(*(style->values())); 00618 while ( it.hasNext() ) { 00619 CSSProperty *property = it.next(); 00620 CSSValueImpl *computedValue = computedStyle->getPropertyCSSValue(property->id()); 00621 DOMString newValue = property->value()->cssText(); 00622 #ifdef DEBUG_COMMANDS 00623 kDebug() << "[new value]:" << property->cssText() << endl; 00624 kDebug() << "[computedValue]:" << computedValue->cssText() << endl; 00625 #endif 00626 if (strcasecmp(computedValue->cssText(), newValue)) { 00627 // we can do better and avoid parsing property 00628 element->getInlineStyleDecls()->setProperty(property->id(), newValue); 00629 } 00630 } 00631 } 00632 00633 void ApplyStyleCommandImpl::doApply() 00634 { 00635 if (endingSelection().state() != Selection::RANGE) 00636 return; 00637 00638 // adjust to the positions we want to use for applying style 00639 Position start(endingSelection().start().equivalentDownstreamPosition().equivalentRangeCompliantPosition()); 00640 Position end(endingSelection().end().equivalentUpstreamPosition()); 00641 #ifdef DEBUG_COMMANDS 00642 kDebug() << "[APPLY STYLE]" << start << end << endl; 00643 printEnclosingBlockTree(start.node()->enclosingBlockFlowElement()); 00644 #endif 00645 00646 if (isBlockLevelStyle(m_style)) { 00647 #ifdef DEBUG_COMMANDS 00648 kDebug() << "[APPLY BLOCK LEVEL STYLE]" << endl; 00649 #endif 00650 ElementImpl *startBlock = start.node()->enclosingBlockFlowElement(); 00651 ElementImpl *endBlock = end.node()->enclosingBlockFlowElement(); 00652 #ifdef DEBUG_COMMANDS 00653 kDebug() << startBlock << startBlock->nodeName() << endl; 00654 #endif 00655 if (startBlock == endBlock && startBlock == start.node()->rootEditableElement()) { 00656 ElementImpl* block = document()->createHTMLElement("DIV"); 00657 #ifdef DEBUG_COMMANDS 00658 kDebug() << "[Create DIV with Style:]" << m_style->cssText() << endl; 00659 #endif 00660 block->setAttribute(ATTR_STYLE, m_style->cssText()); 00661 for (NodeImpl* node = startBlock->firstChild(); node; node = startBlock->firstChild()) { 00662 #ifdef DEBUG_COMMANDS 00663 kDebug() << "[reparent node]" << node << node->nodeName() << endl; 00664 #endif 00665 removeNode(node); 00666 appendNode(block, node); 00667 } 00668 appendNode(startBlock, block); 00669 } else if (startBlock == endBlock) { 00670 // StyleChange styleChange = computeStyleChange(Position(startBlock, 0), m_style); 00671 // kDebug() << "[Modify block with style change:]" << styleChange.cssStyle << endl; 00672 applyStyleChangeOnTheNode(startBlock, m_style); 00673 // startBlock->setAttribute(ATTR_STYLE, styleChange.cssStyle); 00674 } 00675 return; 00676 } 00677 00678 // remove style from the selection 00679 removeStyle(start, end); 00680 bool splitStart = splitTextAtStartIfNeeded(start, end); 00681 if (splitStart) { 00682 start = endingSelection().start(); 00683 end = endingSelection().end(); 00684 } 00685 splitTextAtEndIfNeeded(start, end); 00686 start = endingSelection().start(); 00687 end = endingSelection().end(); 00688 00689 #ifdef DEBUG_COMMANDS 00690 kDebug() << "[start;end]" << start << end << endl; 00691 #endif 00692 if (start.node() == end.node()) { 00693 // simple case...start and end are the same node 00694 applyStyleIfNeeded(start.node(), end.node()); 00695 } else { 00696 NodeImpl *node = start.node(); 00697 while (1) { 00698 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) { 00699 NodeImpl *runStart = node; 00700 while (1) { 00701 if (runStart->parentNode() != node->parentNode() || node->isHTMLElement() || node == end.node() || 00702 (node->renderer() && !node->renderer()->isInline())) { 00703 applyStyleIfNeeded(runStart, node); 00704 break; 00705 } 00706 node = node->traverseNextNode(); 00707 } 00708 } 00709 if (node == end.node()) 00710 break; 00711 node = node->traverseNextNode(); 00712 } 00713 } 00714 } 00715 00716 //------------------------------------------------------------------------------------------ 00717 // ApplyStyleCommandImpl: style-removal helpers 00718 00719 bool ApplyStyleCommandImpl::isHTMLStyleNode(HTMLElementImpl *elem) 00720 { 00721 QListIterator<CSSProperty*> it(*(style()->values())); 00722 while (it.hasNext()) { 00723 CSSProperty *property = it.next(); 00724 switch (property->id()) { 00725 case CSS_PROP_FONT_WEIGHT: 00726 if (elem->id() == ID_B) 00727 return true; 00728 break; 00729 case CSS_PROP_FONT_STYLE: 00730 if (elem->id() == ID_I) 00731 return true; 00732 break; 00733 } 00734 } 00735 00736 return false; 00737 } 00738 00739 void ApplyStyleCommandImpl::removeHTMLStyleNode(HTMLElementImpl *elem) 00740 { 00741 // This node can be removed. 00742 // EDIT FIXME: This does not handle the case where the node 00743 // has attributes. But how often do people add attributes to <B> tags? 00744 // Not so often I think. 00745 assert(elem); 00746 removeNodePreservingChildren(elem); 00747 } 00748 00749 void ApplyStyleCommandImpl::removeCSSStyle(HTMLElementImpl *elem) 00750 { 00751 assert(elem); 00752 00753 CSSStyleDeclarationImpl *decl = elem->inlineStyleDecls(); 00754 if (!decl) 00755 return; 00756 00757 QListIterator<CSSProperty*> it(*(style()->values())); 00758 while ( it.hasNext() ) { 00759 CSSProperty *property = it.next(); 00760 if (decl->getPropertyCSSValue(property->id())) 00761 removeCSSProperty(decl, property->id()); 00762 } 00763 00764 if (elem->id() == ID_SPAN) { 00765 // Check to see if the span is one we added to apply style. 00766 // If it is, and there are no more attributes on the span other than our 00767 // class marker, remove the span. 00768 NamedAttrMapImpl *map = elem->attributes(); 00769 if (map && (map->length() == 1 || (map->length() == 2 && elem->getAttribute(ATTR_STYLE).isEmpty())) && 00770 elem->getAttribute(ATTR_CLASS) == styleSpanClassString()) 00771 removeNodePreservingChildren(elem); 00772 } 00773 } 00774 00775 void ApplyStyleCommandImpl::removeStyle(const Position &start, const Position &end) 00776 { 00777 NodeImpl *node = start.node(); 00778 while (1) { 00779 NodeImpl *next = node->traverseNextNode(); 00780 if (node->isHTMLElement() && nodeFullySelected(node)) { 00781 HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node); 00782 if (isHTMLStyleNode(elem)) 00783 removeHTMLStyleNode(elem); 00784 else 00785 removeCSSStyle(elem); 00786 } 00787 if (node == end.node()) 00788 break; 00789 node = next; 00790 } 00791 } 00792 00793 bool ApplyStyleCommandImpl::nodeFullySelected(const NodeImpl *node) const 00794 { 00795 assert(node); 00796 00797 Position end(endingSelection().end().equivalentUpstreamPosition()); 00798 00799 if (node == end.node()) 00800 return end.offset() >= node->caretMaxOffset(); 00801 00802 for (NodeImpl *child = node->lastChild(); child; child = child->lastChild()) { 00803 if (child == end.node()) 00804 return end.offset() >= child->caretMaxOffset(); 00805 } 00806 00807 return node == end.node() || !node->isAncestor(end.node()); 00808 } 00809 00810 //------------------------------------------------------------------------------------------ 00811 // ApplyStyleCommandImpl: style-application helpers 00812 00813 bool ApplyStyleCommandImpl::splitTextAtStartIfNeeded(const Position &start, const Position &end) 00814 { 00815 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) { 00816 #ifdef DEBUG_COMMANDS 00817 kDebug() << "[split start]" << start.offset() << start.node()->caretMinOffset() << start.node()->caretMaxOffset() << endl; 00818 #endif 00819 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0; 00820 TextImpl *text = static_cast<TextImpl *>(start.node()); 00821 RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, start.offset()); 00822 applyCommandToComposite(cmd); 00823 setEndingSelection(Selection(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment))); 00824 return true; 00825 } 00826 return false; 00827 } 00828 00829 NodeImpl *ApplyStyleCommandImpl::splitTextAtEndIfNeeded(const Position &start, const Position &end) 00830 { 00831 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) { 00832 #ifdef DEBUG_COMMANDS 00833 kDebug() << "[split end]" << end.offset() << end.node()->caretMinOffset() << end.node()->caretMaxOffset() << endl; 00834 #endif 00835 TextImpl *text = static_cast<TextImpl *>(end.node()); 00836 RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, end.offset()); 00837 applyCommandToComposite(cmd); 00838 NodeImpl *startNode = start.node() == end.node() ? cmd->node()->previousSibling() : start.node(); 00839 assert(startNode); 00840 setEndingSelection(Selection(Position(startNode, start.offset()), Position(cmd->node()->previousSibling(), cmd->node()->previousSibling()->caretMaxOffset()))); 00841 return cmd->node()->previousSibling(); 00842 } 00843 return end.node(); 00844 } 00845 00846 void ApplyStyleCommandImpl::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element) 00847 { 00848 assert(startNode); 00849 assert(endNode); 00850 assert(element); 00851 00852 NodeImpl *node = startNode; 00853 while (1) { 00854 NodeImpl *next = node->traverseNextNode(); 00855 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) { 00856 removeNode(node); 00857 appendNode(element, node); 00858 } 00859 if (node == endNode) 00860 break; 00861 node = next; 00862 } 00863 } 00864 00865 static bool /*ApplyStyleCommandImpl::*/checkIfNewStylingNeeded(ElementImpl* element, CSSStyleDeclarationImpl *style) 00866 { 00867 CSSStyleDeclarationImpl *computedStyle = element->document()->defaultView()->getComputedStyle(element, 0); 00868 assert(computedStyle); 00869 #ifdef DEBUG_COMMANDS 00870 kDebug() << "[check styling]" << element << endl; 00871 #endif 00872 00873 QListIterator<CSSProperty*> it(*(style->values())); 00874 while ( it.hasNext() ) { 00875 CSSProperty *property = it.next(); 00876 CSSValueImpl *computedValue = computedStyle->getPropertyCSSValue(property->id()); 00877 DOMString newValue = property->value()->cssText(); 00878 #ifdef DEBUG_COMMANDS 00879 kDebug() << "[new value]:" << property->cssText() << endl; 00880 kDebug() << "[computedValue]:" << computedValue->cssText() << endl; 00881 #endif 00882 if (strcasecmp(computedValue->cssText(), newValue)) 00883 return true; 00884 } 00885 return false; 00886 } 00887 00888 void ApplyStyleCommandImpl::applyStyleIfNeeded(DOM::NodeImpl *startNode, DOM::NodeImpl *endNode) 00889 { 00890 ElementImpl *parent = Position(startNode, 0).element(); 00891 if (!checkIfNewStylingNeeded(parent, style())) 00892 return; 00893 ElementImpl *styleElement = 0; 00894 if (parent->id() == ID_SPAN && parent->firstChild() == startNode && parent->lastChild() == endNode) { 00895 styleElement = parent; 00896 } else { 00897 styleElement = document()->createHTMLElement("SPAN"); 00898 styleElement->setAttribute(ATTR_CLASS, styleSpanClassString()); 00899 insertNodeBefore(styleElement, startNode); 00900 surroundNodeRangeWithElement(startNode, endNode, styleElement); 00901 } 00902 applyStyleChangeOnTheNode(styleElement, style()); 00903 } 00904 00905 bool ApplyStyleCommandImpl::currentlyHasStyle(const Position &pos, const CSSProperty *property) const 00906 { 00907 assert(pos.notEmpty()); 00908 kDebug() << pos << endl; 00909 CSSStyleDeclarationImpl *decl = document()->defaultView()->getComputedStyle(pos.element(), 0); 00910 assert(decl); 00911 CSSValueImpl *value = decl->getPropertyCSSValue(property->id()); 00912 return strcasecmp(value->cssText(), property->value()->cssText()) == 0; 00913 } 00914 00915 ApplyStyleCommandImpl::StyleChange ApplyStyleCommandImpl::computeStyleChange(const Position &insertionPoint, CSSStyleDeclarationImpl *style) 00916 { 00917 assert(insertionPoint.notEmpty()); 00918 assert(style); 00919 00920 StyleChange styleChange; 00921 00922 QListIterator<CSSProperty*> it(*(style->values())); 00923 while ( it.hasNext() ) { 00924 CSSProperty *property = it.next(); 00925 #ifdef DEBUG_COMMANDS 00926 kDebug() << "[CSS property]:" << property->cssText() << endl; 00927 #endif 00928 if (!currentlyHasStyle(insertionPoint, property)) { 00929 #ifdef DEBUG_COMMANDS 00930 kDebug() << "[Add to style change]" << endl; 00931 #endif 00932 switch (property->id()) { 00933 case CSS_PROP_FONT_WEIGHT: 00934 if (strcasecmp(property->value()->cssText(), "bold") == 0) 00935 styleChange.applyBold = true; 00936 else 00937 styleChange.cssStyle += property->cssText(); 00938 break; 00939 case CSS_PROP_FONT_STYLE: { 00940 DOMString cssText(property->value()->cssText()); 00941 if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0) 00942 styleChange.applyItalic = true; 00943 else 00944 styleChange.cssStyle += property->cssText(); 00945 } 00946 break; 00947 default: 00948 styleChange.cssStyle += property->cssText(); 00949 break; 00950 } 00951 } 00952 } 00953 return styleChange; 00954 } 00955 00956 Position ApplyStyleCommandImpl::positionInsertionPoint(Position pos) 00957 { 00958 if (pos.node()->isTextNode() && (pos.offset() > 0 && pos.offset() < pos.node()->maxOffset())) { 00959 RefPtr<SplitTextNodeCommandImpl> split = new SplitTextNodeCommandImpl(document(), static_cast<TextImpl *>(pos.node()), pos.offset()); 00960 split->apply(); 00961 pos = Position(split->node(), 0); 00962 } 00963 #if 0 00964 // EDIT FIXME: If modified to work with the internals of applying style, 00965 // this code can work to optimize cases where a style change is taking place on 00966 // a boundary between nodes where one of the nodes has the desired style. In other 00967 // words, it is possible for content to be merged into existing nodes rather than adding 00968 // additional markup. 00969 if (currentlyHasStyle(pos)) 00970 return pos; 00971 00972 // try next node 00973 if (pos.offset() >= pos.node()->caretMaxOffset()) { 00974 NodeImpl *nextNode = pos.node()->traverseNextNode(); 00975 if (nextNode) { 00976 Position next = Position(nextNode, 0); 00977 if (currentlyHasStyle(next)) 00978 return next; 00979 } 00980 } 00981 00982 // try previous node 00983 if (pos.offset() <= pos.node()->caretMinOffset()) { 00984 NodeImpl *prevNode = pos.node()->traversePreviousNode(); 00985 if (prevNode) { 00986 Position prev = Position(prevNode, prevNode->maxOffset()); 00987 if (currentlyHasStyle(prev)) 00988 return prev; 00989 } 00990 } 00991 #endif 00992 00993 return pos; 00994 } 00995 00996 //------------------------------------------------------------------------------------------ 00997 // DeleteCollapsibleWhitespaceCommandImpl 00998 00999 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document) 01000 : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_hasSelectionToCollapse(false) 01001 { 01002 } 01003 01004 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document, const Selection &selection) 01005 : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_selectionToCollapse(selection), m_hasSelectionToCollapse(true) 01006 { 01007 } 01008 01009 DeleteCollapsibleWhitespaceCommandImpl::~DeleteCollapsibleWhitespaceCommandImpl() 01010 { 01011 } 01012 01013 static bool shouldDeleteUpstreamPosition(const Position &pos) 01014 { 01015 if (!pos.node()->isTextNode()) 01016 return false; 01017 01018 RenderObject *renderer = pos.node()->renderer(); 01019 if (!renderer) 01020 return true; 01021 01022 TextImpl *textNode = static_cast<TextImpl *>(pos.node()); 01023 if (pos.offset() >= (long)textNode->length()) 01024 return false; 01025 01026 if (pos.isLastRenderedPositionInEditableBlock()) 01027 return false; 01028 01029 if (pos.isFirstRenderedPositionOnLine() || pos.isLastRenderedPositionOnLine()) 01030 return false; 01031 01032 return false; 01033 // TODO we need to match DOM - Rendered offset first 01034 // RenderText *textRenderer = static_cast<RenderText *>(renderer); 01035 // for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { 01036 // if (pos.offset() < box->m_start) { 01037 // return true; 01038 // } 01039 // if (pos.offset() >= box->m_start && pos.offset() < box->m_start + box->m_len) 01040 // return false; 01041 // } 01042 // 01043 // return true; 01044 } 01045 01046 Position DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace(const Position &pos) 01047 { 01048 Position upstream = pos.equivalentUpstreamPosition(); 01049 Position downstream = pos.equivalentDownstreamPosition(); 01050 #ifdef DEBUG_COMMANDS 01051 kDebug() << "[pos]" << pos << endl; 01052 kDebug() << "[upstream:downstream]" << upstream << downstream << endl; 01053 printEnclosingBlockTree(pos.node()); 01054 #endif 01055 01056 bool del = shouldDeleteUpstreamPosition(upstream); 01057 #ifdef DEBUG_COMMANDS 01058 kDebug() << "[delete upstream]" << del << endl; 01059 #endif 01060 01061 if (upstream == downstream) 01062 return upstream; 01063 01064 #ifdef DEBUG_COMMANDS 01065 PositionIterator iter(upstream); 01066 kDebug() << "[before print]" << endl; 01067 for (iter.next(); iter.current() != downstream; iter.next()) 01068 kDebug() << "[iterate]" << iter.current() << endl; 01069 kDebug() << "[after print]" << endl; 01070 #endif 01071 01072 PositionIterator it(upstream); 01073 Position deleteStart = upstream; 01074 if (!del) { 01075 deleteStart = it.peekNext(); 01076 if (deleteStart == downstream) 01077 return upstream; 01078 } 01079 01080 Position endingPosition = upstream; 01081 01082 while (it.current() != downstream) { 01083 Position next = it.peekNext(); 01084 #ifdef DEBUG_COMMANDS 01085 kDebug() << "[iterate and delete]" << next << endl; 01086 #endif 01087 if (next.node() != deleteStart.node()) { 01088 // TODO assert(deleteStart.node()->isTextNode()); 01089 if (deleteStart.node()->isTextNode()) { 01090 TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node()); 01091 unsigned long count = it.current().offset() - deleteStart.offset(); 01092 if (count == textNode->length()) { 01093 #ifdef DEBUG_COMMANDS 01094 kDebug(6200) << " removeNodeAndPrune 1:" << textNode; 01095 #endif 01096 if (textNode == endingPosition.node()) 01097 endingPosition = Position(next.node(), next.node()->caretMinOffset()); 01098 removeNodeAndPrune(textNode); 01099 } else { 01100 #ifdef DEBUG_COMMANDS 01101 kDebug(6200) << " deleteText 1:" << textNode << "t len:" << textNode->length()<<"start:" << deleteStart.offset() << "del len:" << (it.current().offset() - deleteStart.offset()); 01102 #endif 01103 deleteText(textNode, deleteStart.offset(), count); 01104 } 01105 } else { 01106 #ifdef DEBUG_COMMANDS 01107 kDebug() << "[not text node is not supported yet]" << endl; 01108 #endif 01109 } 01110 deleteStart = next; 01111 } else if (next == downstream) { 01112 assert(deleteStart.node() == downstream.node()); 01113 assert(downstream.node()->isTextNode()); 01114 TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node()); 01115 unsigned long count = downstream.offset() - deleteStart.offset(); 01116 assert(count <= textNode->length()); 01117 if (count == textNode->length()) { 01118 #ifdef DEBUG_COMMANDS 01119 kDebug(6200) << " removeNodeAndPrune 2:"<<textNode; 01120 #endif 01121 removeNodeAndPrune(textNode); 01122 } else { 01123 #ifdef DEBUG_COMMANDS 01124 kDebug(6200) << " deleteText 2:"<< textNode<< "t len:" << textNode->length() <<"start:" <<deleteStart.offset() << "del len:" << count; 01125 #endif 01126 deleteText(textNode, deleteStart.offset(), count); 01127 m_charactersDeleted = count; 01128 endingPosition = Position(downstream.node(), downstream.offset() - m_charactersDeleted); 01129 } 01130 } 01131 01132 it.setPosition(next); 01133 } 01134 01135 return endingPosition; 01136 } 01137 01138 void DeleteCollapsibleWhitespaceCommandImpl::doApply() 01139 { 01140 // If selection has not been set to a custom selection when the command was created, 01141 // use the current ending selection. 01142 if (!m_hasSelectionToCollapse) 01143 m_selectionToCollapse = endingSelection(); 01144 int state = m_selectionToCollapse.state(); 01145 if (state == Selection::CARET) { 01146 Position endPosition = deleteWhitespace(m_selectionToCollapse.start()); 01147 setEndingSelection(endPosition); 01148 #ifdef DEBUG_COMMANDS 01149 kDebug(6200) << "-----------------------------------------------------"; 01150 #endif 01151 } 01152 else if (state == Selection::RANGE) { 01153 Position startPosition = deleteWhitespace(m_selectionToCollapse.start()); 01154 #ifdef DEBUG_COMMANDS 01155 kDebug(6200) << "-----------------------------------------------------"; 01156 #endif 01157 Position endPosition = m_selectionToCollapse.end(); 01158 if (m_charactersDeleted > 0 && startPosition.node() == endPosition.node()) { 01159 #ifdef DEBUG_COMMANDS 01160 kDebug(6200) << "adjust end position by" << m_charactersDeleted; 01161 #endif 01162 endPosition = Position(endPosition.node(), endPosition.offset() - m_charactersDeleted); 01163 } 01164 endPosition = deleteWhitespace(endPosition); 01165 setEndingSelection(Selection(startPosition, endPosition)); 01166 #ifdef DEBUG_COMMANDS 01167 kDebug(6200) << "====================================================="; 01168 #endif 01169 } 01170 } 01171 01172 //------------------------------------------------------------------------------------------ 01173 // DeleteSelectionCommandImpl 01174 01175 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document) 01176 : CompositeEditCommandImpl(document), m_hasSelectionToDelete(false) 01177 { 01178 } 01179 01180 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document, const Selection &selection) 01181 : CompositeEditCommandImpl(document), m_selectionToDelete(selection), m_hasSelectionToDelete(true) 01182 { 01183 } 01184 01185 DeleteSelectionCommandImpl::~DeleteSelectionCommandImpl() 01186 { 01187 } 01188 01189 void DeleteSelectionCommandImpl::joinTextNodesWithSameStyle() 01190 { 01191 Selection selection = endingSelection(); 01192 01193 if (selection.state() != Selection::CARET) 01194 return; 01195 01196 Position pos(selection.start()); 01197 01198 if (!pos.node()->isTextNode()) 01199 return; 01200 01201 TextImpl *textNode = static_cast<TextImpl *>(pos.node()); 01202 01203 if (pos.offset() == 0) { 01204 PositionIterator it(pos); 01205 Position prev = it.previous(); 01206 if (prev == pos) 01207 return; 01208 if (prev.node()->isTextNode()) { 01209 TextImpl *prevTextNode = static_cast<TextImpl *>(prev.node()); 01210 if (textNodesAreJoinable(prevTextNode, textNode)) { 01211 joinTextNodes(prevTextNode, textNode); 01212 setEndingSelection(Position(textNode, prevTextNode->length())); 01213 #ifdef DEBUG_COMMANDS 01214 kDebug(6200) << "joinTextNodesWithSameStyle [1]"; 01215 #endif 01216 } 01217 } 01218 } else if (pos.offset() == (long)textNode->length()) { 01219 PositionIterator it(pos); 01220 Position next = it.next(); 01221 if (next == pos) 01222 return; 01223 if (next.node()->isTextNode()) { 01224 TextImpl *nextTextNode = static_cast<TextImpl *>(next.node()); 01225 if (textNodesAreJoinable(textNode, nextTextNode)) { 01226 joinTextNodes(textNode, nextTextNode); 01227 setEndingSelection(Position(nextTextNode, pos.offset())); 01228 #ifdef DEBUG_COMMANDS 01229 kDebug(6200) << "joinTextNodesWithSameStyle [2]"; 01230 #endif 01231 } 01232 } 01233 } 01234 } 01235 01236 bool DeleteSelectionCommandImpl::containsOnlyWhitespace(const Position &start, const Position &end) 01237 { 01238 // Returns whether the range contains only whitespace characters. 01239 // This is inclusive of the start, but not of the end. 01240 PositionIterator it(start); 01241 while (!it.atEnd()) { 01242 if (!it.current().node()->isTextNode()) 01243 return false; 01244 const DOMString &text = static_cast<TextImpl *>(it.current().node())->data(); 01245 // EDIT FIXME: signed/unsigned mismatch 01246 if (text.length() > INT_MAX) 01247 return false; 01248 if (it.current().offset() < (int)text.length() && !isWS(text[it.current().offset()])) 01249 return false; 01250 it.next(); 01251 if (it.current() == end) 01252 break; 01253 } 01254 return true; 01255 } 01256 01257 void DeleteSelectionCommandImpl::deleteContentInsideNode(NodeImpl *node, int startOffset, int endOffset) 01258 { 01259 #ifdef DEBUG_COMMANDS 01260 kDebug() << "[Delete content inside node]" << node << startOffset << endOffset << endl; 01261 #endif 01262 if (node->isTextNode()) { 01263 // check if nothing to delete 01264 if (startOffset == endOffset) 01265 return; 01266 // check if node is fully covered then remove node completely 01267 if (!startOffset && endOffset == node->maxOffset()) { 01268 removeNodeAndPrune(node); 01269 return; 01270 } 01271 // delete only substring 01272 deleteText(static_cast<TextImpl*>(node), startOffset, endOffset - startOffset); 01273 return; 01274 } 01275 #ifdef DEBUG_COMMANDS 01276 kDebug() << "[non-text node] not supported" << endl; 01277 #endif 01278 } 01279 01280 void DeleteSelectionCommandImpl::deleteContentBeforeOffset(NodeImpl *node, int offset) 01281 { 01282 deleteContentInsideNode(node, 0, offset); 01283 } 01284 01285 void DeleteSelectionCommandImpl::deleteContentAfterOffset(NodeImpl *node, int offset) 01286 { 01287 if (node->isTextNode()) 01288 deleteContentInsideNode(node, offset, node->maxOffset()); 01289 } 01290 01291 void DeleteSelectionCommandImpl::doApply() 01292 { 01293 // If selection has not been set to a custom selection when the command was created, 01294 // use the current ending selection. 01295 if (!m_hasSelectionToDelete) 01296 m_selectionToDelete = endingSelection(); 01297 01298 if (m_selectionToDelete.state() != Selection::RANGE) 01299 return; 01300 01301 deleteCollapsibleWhitespace(m_selectionToDelete); 01302 Selection selection = endingSelection(); 01303 01304 Position upstreamStart(selection.start().equivalentUpstreamPosition()); 01305 Position downstreamStart(selection.start().equivalentDownstreamPosition()); 01306 Position upstreamEnd(selection.end().equivalentUpstreamPosition()); 01307 Position downstreamEnd(selection.end().equivalentDownstreamPosition()); 01308 01309 NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement(); 01310 NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement(); 01311 01312 #ifdef DEBUG_COMMANDS 01313 kDebug() << "[Delete:Start]" << upstreamStart << downstreamStart << endl; 01314 kDebug() << "[Delete:End]" << upstreamEnd << downstreamEnd << endl; 01315 printEnclosingBlockTree(upstreamStart.node()); 01316 #endif 01317 if (startBlock != endBlock) 01318 printEnclosingBlockTree(downstreamEnd.node()); 01319 01320 if (upstreamStart == downstreamEnd) 01321 // after collapsing whitespace, selection is empty...no work to do 01322 return; 01323 01324 // remove all the nodes that are completely covered by the selection 01325 if (upstreamStart.node() != downstreamEnd.node()) { 01326 NodeImpl *node, *next; 01327 for (node = upstreamStart.node()->traverseNextNode(); node && node != downstreamEnd.node(); node = next) { 01328 #ifdef DEBUG_COMMANDS 01329 kDebug() << "[traverse and delete]" << node << (node->renderer() && node->renderer()->isEditable()) << endl; 01330 #endif 01331 next = node->traverseNextNode(); 01332 if (node->renderer() && node->renderer()->isEditable()) 01333 removeNode(node); // removeAndPrune? 01334 } 01335 } 01336 01337 // if we have different blocks then merge content of the second into first one 01338 if (startBlock != endBlock && startBlock->parentNode() == endBlock->parentNode()) { 01339 NodeImpl *node = endBlock->firstChild(); 01340 while (node) { 01341 NodeImpl *moveNode = node; 01342 node = node->nextSibling(); 01343 removeNode(moveNode); 01344 appendNode(startBlock, moveNode); 01345 } 01346 } 01347 01348 if (upstreamStart.node() == downstreamEnd.node()) 01349 deleteContentInsideNode(upstreamEnd.node(), upstreamStart.offset(), downstreamEnd.offset()); 01350 else { 01351 deleteContentAfterOffset(upstreamStart.node(), upstreamStart.offset()); 01352 deleteContentBeforeOffset(downstreamEnd.node(), downstreamEnd.offset()); 01353 } 01354 01355 setEndingSelection(upstreamStart); 01356 #if 0 01357 Position endingPosition; 01358 bool adjustEndingPositionDownstream = false; 01359 01360 bool onlyWhitespace = containsOnlyWhitespace(upstreamStart, downstreamEnd); 01361 kDebug() << "[OnlyWhitespace]" << onlyWhitespace << endl; 01362 01363 bool startCompletelySelected = !onlyWhitespace && 01364 (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset() && 01365 ((downstreamStart.node() != upstreamEnd.node()) || 01366 (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset()))); 01367 01368 bool endCompletelySelected = !onlyWhitespace && 01369 (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset() && 01370 ((downstreamStart.node() != upstreamEnd.node()) || 01371 (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset()))); 01372 01373 kDebug() << "[{start:end}CompletelySelected]" << startCompletelySelected << endCompletelySelected << endl; 01374 01375 unsigned long startRenderedOffset = downstreamStart.renderedOffset(); 01376 01377 bool startAtStartOfRootEditableElement = startRenderedOffset == 0 && downstreamStart.inFirstEditableInRootEditableElement(); 01378 bool startAtStartOfBlock = startAtStartOfRootEditableElement || 01379 (startRenderedOffset == 0 && downstreamStart.inFirstEditableInContainingEditableBlock()); 01380 bool endAtEndOfBlock = downstreamEnd.isLastRenderedPositionInEditableBlock(); 01381 01382 kDebug() << "[startAtStartOfRootEditableElement]" << startAtStartOfRootEditableElement << endl; 01383 kDebug() << "[startAtStartOfBlock]" << startAtStartOfBlock << endl; 01384 kDebug() << "[endAtEndOfBlock]" << endAtEndOfBlock << endl; 01385 01386 NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement(); 01387 NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement(); 01388 bool startBlockEndBlockAreSiblings = startBlock->parentNode() == endBlock->parentNode(); 01389 01390 kDebug() << "[startBlockEndBlockAreSiblings]" << startBlockEndBlockAreSiblings << startBlock << endBlock << endl; 01391 01392 debugPosition("upstreamStart: ", upstreamStart); 01393 debugPosition("downstreamStart: ", downstreamStart); 01394 debugPosition("upstreamEnd: ", upstreamEnd); 01395 debugPosition("downstreamEnd: ", downstreamEnd); 01396 kDebug(6200) << "start selected:" << (startCompletelySelected ? "YES" : "NO"); 01397 kDebug(6200) << "at start block:" << (startAtStartOfBlock ? "YES" : "NO"); 01398 kDebug(6200) << "at start root block:"<< (startAtStartOfRootEditableElement ? "YES" : "NO"); 01399 kDebug(6200) << "at end block:"<< (endAtEndOfBlock ? "YES" : "NO"); 01400 kDebug(6200) << "only whitespace:"<< (onlyWhitespace ? "YES" : "NO"); 01401 01402 // Determine where to put the caret after the deletion 01403 if (startAtStartOfBlock) { 01404 kDebug(6200) << "ending position case 1"; 01405 endingPosition = Position(startBlock, 0); 01406 adjustEndingPositionDownstream = true; 01407 } else if (!startCompletelySelected) { 01408 kDebug(6200) << "ending position case 2"; 01409 endingPosition = upstreamEnd; // FIXME ??????????? upstreamStart; 01410 if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1) 01411 adjustEndingPositionDownstream = true; 01412 } else if (upstreamStart != downstreamStart) { 01413 kDebug(6200) << "ending position case 3"; 01414 endingPosition = upstreamStart; 01415 if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1) 01416 adjustEndingPositionDownstream = true; 01417 } 01418 01419 // 01420 // Figure out the whitespace conversions to do 01421 // 01422 if ((startAtStartOfBlock && !endAtEndOfBlock) || (!startCompletelySelected && adjustEndingPositionDownstream)) { 01423 // convert trailing whitespace 01424 Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition()); 01425 if (trailing.notEmpty()) { 01426 debugPosition("convertTrailingWhitespace: ", trailing); 01427 Position collapse = trailing.nextCharacterPosition(); 01428 if (collapse != trailing) 01429 deleteCollapsibleWhitespace(collapse); 01430 TextImpl *textNode = static_cast<TextImpl *>(trailing.node()); 01431 replaceText(textNode, trailing.offset(), 1, nonBreakingSpaceString()); 01432 } 01433 } else if (!startAtStartOfBlock && endAtEndOfBlock) { 01434 // convert leading whitespace 01435 Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition()); 01436 if (leading.notEmpty()) { 01437 debugPosition("convertLeadingWhitespace: ", leading); 01438 TextImpl *textNode = static_cast<TextImpl *>(leading.node()); 01439 replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString()); 01440 } 01441 } else if (!startAtStartOfBlock && !endAtEndOfBlock) { 01442 // convert contiguous whitespace 01443 Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition()); 01444 Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition()); 01445 if (leading.notEmpty() && trailing.notEmpty()) { 01446 debugPosition("convertLeadingWhitespace [contiguous]: ", leading); 01447 TextImpl *textNode = static_cast<TextImpl *>(leading.node()); 01448 replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString()); 01449 } 01450 } 01451 01452 // 01453 // Do the delete 01454 // 01455 NodeImpl *n = downstreamStart.node()->traverseNextNode(); 01456 kDebug() << "[n]" << n << endl; 01457 01458 // work on start node 01459 if (startCompletelySelected) { 01460 kDebug(6200) << "start node delete case 1"; 01461 removeNodeAndPrune(downstreamStart.node(), startBlock); 01462 } else if (onlyWhitespace) { 01463 // Selection only contains whitespace. This is really a special-case to 01464 // handle significant whitespace that is collapsed at the end of a line, 01465 // but also handles deleting a space in mid-line. 01466 kDebug(6200) << "start node delete case 2"; 01467 assert(upstreamStart.node()->isTextNode()); 01468 TextImpl *text = static_cast<TextImpl *>(upstreamStart.node()); 01469 int offset = upstreamStart.offset(); 01470 // EDIT FIXME: Signed/unsigned mismatch 01471 int length = text->length(); 01472 if (length == upstreamStart.offset()) 01473 offset--; 01474 // FIXME ??? deleteText(text, offset, 1); 01475 } else if (downstreamStart.node()->isTextNode()) { 01476 kDebug(6200) << "start node delete case 3"; 01477 TextImpl *text = static_cast<TextImpl *>(downstreamStart.node()); 01478 int endOffset = text == upstreamEnd.node() ? upstreamEnd.offset() : text->length(); 01479 if (endOffset > downstreamStart.offset()) { 01480 deleteText(text, downstreamStart.offset(), endOffset - downstreamStart.offset()); 01481 } 01482 } else { 01483 // we have clipped the end of a non-text element 01484 // the offset must be 1 here. if it is, do nothing and move on. 01485 kDebug(6200) << "start node delete case 4"; 01486 assert(downstreamStart.offset() == 1); 01487 } 01488 01489 if (n && !onlyWhitespace && downstreamStart.node() != upstreamEnd.node()) { 01490 // work on intermediate nodes 01491 while (n && n != upstreamEnd.node()) { 01492 NodeImpl *d = n; 01493 n = n->traverseNextNode(); 01494 if (d->renderer() && d->renderer()->isEditable()) 01495 removeNodeAndPrune(d, startBlock); 01496 } 01497 if (!n) 01498 return; 01499 01500 // work on end node 01501 assert(n == upstreamEnd.node()); 01502 if (endCompletelySelected) { 01503 removeNodeAndPrune(upstreamEnd.node(), startBlock); 01504 } 01505 else if (upstreamEnd.node()->isTextNode()) { 01506 if (upstreamEnd.offset() > 0) { 01507 TextImpl *text = static_cast<TextImpl *>(upstreamEnd.node()); 01508 deleteText(text, 0, upstreamEnd.offset()); 01509 } 01510 } 01511 else { 01512 // we have clipped the beginning of a non-text element 01513 // the offset must be 0 here. if it is, do nothing and move on. 01514 assert(downstreamStart.offset() == 0); 01515 } 01516 } 01517 01518 // Do block merge if start and end of selection are in different blocks 01519 // and the blocks are siblings. This is a first cut at this rule arrived 01520 // at by doing a bunch of edits and settling on the behavior that made 01521 // the most sense. This could change in the future as we get more 01522 // experience with how this should behave. 01523 if (startBlock != endBlock && startBlockEndBlockAreSiblings) { 01524 kDebug(6200) << "merging content to start block"; 01525 NodeImpl *node = endBlock->firstChild(); 01526 while (node) { 01527 NodeImpl *moveNode = node; 01528 node = node->nextSibling(); 01529 removeNode(moveNode); 01530 appendNode(startBlock, moveNode); 01531 } 01532 } 01533 01534 if (adjustEndingPositionDownstream) { 01535 kDebug(6200) << "adjust ending position downstream"; 01536 endingPosition = endingPosition.equivalentDownstreamPosition(); 01537 } 01538 01539 debugPosition("ending position: ", endingPosition); 01540 setEndingSelection(endingPosition); 01541 01542 kDebug(6200) << "-----------------------------------------------------"; 01543 #endif 01544 } 01545 01546 //------------------------------------------------------------------------------------------ 01547 // DeleteTextCommandImpl 01548 01549 DeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, long count) 01550 : EditCommandImpl(document), m_node(node), m_offset(offset), m_count(count) 01551 { 01552 assert(m_node); 01553 assert(m_offset >= 0); 01554 assert(m_count >= 0); 01555 01556 m_node->ref(); 01557 } 01558 01559 DeleteTextCommandImpl::~DeleteTextCommandImpl() 01560 { 01561 if (m_node) 01562 m_node->deref(); 01563 } 01564 01565 void DeleteTextCommandImpl::doApply() 01566 { 01567 assert(m_node); 01568 01569 int exceptionCode = 0; 01570 m_text = m_node->substringData(m_offset, m_count, exceptionCode); 01571 assert(exceptionCode == 0); 01572 01573 m_node->deleteData(m_offset, m_count, exceptionCode); 01574 assert(exceptionCode == 0); 01575 } 01576 01577 void DeleteTextCommandImpl::doUnapply() 01578 { 01579 assert(m_node); 01580 assert(!m_text.isEmpty()); 01581 01582 int exceptionCode = 0; 01583 m_node->insertData(m_offset, m_text, exceptionCode); 01584 assert(exceptionCode == 0); 01585 } 01586 01587 //------------------------------------------------------------------------------------------ 01588 // InputNewlineCommandImpl 01589 01590 InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document) 01591 : CompositeEditCommandImpl(document) 01592 { 01593 } 01594 01595 InputNewlineCommandImpl::~InputNewlineCommandImpl() 01596 { 01597 } 01598 01599 void InputNewlineCommandImpl::insertNodeAfterPosition(NodeImpl *node, const Position &pos) 01600 { 01601 // Insert the BR after the caret position. In the case the 01602 // position is a block, do an append. We don't want to insert 01603 // the BR *after* the block. 01604 Position upstream(pos.equivalentUpstreamPosition()); 01605 NodeImpl *cb = pos.node()->enclosingBlockFlowElement(); 01606 if (cb == pos.node()) 01607 appendNode(cb, node); 01608 else 01609 insertNodeAfter(node, pos.node()); 01610 } 01611 01612 void InputNewlineCommandImpl::insertNodeBeforePosition(NodeImpl *node, const Position &pos) 01613 { 01614 // Insert the BR after the caret position. In the case the 01615 // position is a block, do an append. We don't want to insert 01616 // the BR *before* the block. 01617 Position upstream(pos.equivalentUpstreamPosition()); 01618 NodeImpl *cb = pos.node()->enclosingBlockFlowElement(); 01619 if (cb == pos.node()) 01620 appendNode(cb, node); 01621 else 01622 insertNodeBefore(node, pos.node()); 01623 } 01624 01625 void InputNewlineCommandImpl::doApply() 01626 { 01627 deleteSelection(); 01628 Selection selection = endingSelection(); 01629 int exceptionCode = 0; 01630 01631 NodeImpl *enclosingBlock = selection.start().node()->enclosingBlockFlowElement(); 01632 kDebug() << enclosingBlock->nodeName() << endl; 01633 if (enclosingBlock->id() == ID_LI) { 01634 // need to insert new list item or split existing one into 2 01635 // consider example: <li>x<u>x<b>x|x</b>x</u>x</li> (| - caret position) 01636 // result should look like: <li>x<u>x<b>x</b></u></li><li><u>|x<b>x</b></u></li> 01637 // idea is to walk up to the li item and split and reattach correspondent nodes 01638 #ifdef DEBUG_COMMANDS 01639 kDebug() << "[insert new list item]" << selection << endl; 01640 printEnclosingBlockTree(selection.start().node()); 01641 #endif 01642 Position pos(selection.start().equivalentDownstreamPosition()); 01643 NodeImpl *node = pos.node(); 01644 bool atBlockStart = pos.atStartOfContainingEditableBlock(); 01645 bool atBlockEnd = pos.isLastRenderedPositionInEditableBlock(); 01646 // split text node into 2 if we are in the middle 01647 if (node->isTextNode() && !atBlockStart && !atBlockEnd) { 01648 TextImpl *textNode = static_cast<TextImpl*>(node); 01649 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode)); 01650 deleteText(textNode, 0, pos.offset()); 01651 insertNodeBefore(textBeforeNode, textNode); 01652 pos = Position(textNode, 0); 01653 setEndingSelection(pos); 01654 01655 // walk up and reattach 01656 while (true) { 01657 #ifdef DEBUG_COMMANDS 01658 kDebug() << "[handle node]" << node << endl; 01659 printEnclosingBlockTree(enclosingBlock->parent()); 01660 #endif 01661 NodeImpl *parent = node->parent(); 01662 // FIXME copy attributes, styles etc too 01663 RefPtr<NodeImpl> newParent = parent->cloneNode(false); 01664 insertNodeAfter(newParent.get(), parent); 01665 for (NodeImpl *nextSibling = 0; node; node = nextSibling) { 01666 #ifdef DEBUG_COMMANDS 01667 kDebug() << "[reattach sibling]" << node << endl; 01668 #endif 01669 nextSibling = node->nextSibling(); 01670 removeNode(node); 01671 appendNode(newParent.get(), node); 01672 } 01673 node = newParent.get(); 01674 if (parent == enclosingBlock) 01675 break; 01676 } 01677 } else if (node->isTextNode()) { 01678 // insert <br> node either as previous list or the next one 01679 if (atBlockStart) { 01680 ElementImpl *listItem = document()->createHTMLElement("LI"); 01681 insertNodeBefore(listItem, enclosingBlock); 01682 } else { 01683 ElementImpl *listItem = document()->createHTMLElement("LI"); 01684 insertNodeAfter(listItem, enclosingBlock); 01685 } 01686 } 01687 01688 #ifdef DEBUG_COMMANDS 01689 kDebug() << "[result]" << endl; 01690 printEnclosingBlockTree(enclosingBlock->parent()); 01691 #endif 01692 // FIXME set selection after operation 01693 return; 01694 } 01695 01696 ElementImpl *breakNode = document()->createHTMLElement("BR"); 01697 // assert(exceptionCode == 0); 01698 01699 #ifdef DEBUG_COMMANDS 01700 kDebug() << "[insert break]" << selection << endl; 01701 printEnclosingBlockTree(enclosingBlock); 01702 #endif 01703 01704 NodeImpl *nodeToInsert = breakNode; 01705 // Handle the case where there is a typing style. 01706 if (document()->part()->editor()->typingStyle()) { 01707 int exceptionCode = 0; 01708 ElementImpl *styleElement = createTypingStyleElement(); 01709 styleElement->appendChild(breakNode, exceptionCode); 01710 assert(exceptionCode == 0); 01711 nodeToInsert = styleElement; 01712 } 01713 01714 Position pos(selection.start().equivalentDownstreamPosition()); 01715 bool atStart = pos.offset() <= pos.node()->caretMinOffset(); 01716 bool atEndOfBlock = pos.isLastRenderedPositionInEditableBlock(); 01717 01718 #ifdef DEBUG_COMMANDS 01719 kDebug() << "[pos]" << pos << atStart << atEndOfBlock << endl; 01720 #endif 01721 01722 if (atEndOfBlock) { 01723 #ifdef DEBUG_COMMANDS 01724 kDebug(6200) << "input newline case 1"; 01725 #endif 01726 // Insert an "extra" BR at the end of the block. This makes the "real" BR we want 01727 // to insert appear in the rendering without any significant side effects (and no 01728 // real worries either since you can't arrow past this extra one. 01729 insertNodeAfterPosition(nodeToInsert, pos); 01730 exceptionCode = 0; 01731 ElementImpl *extraBreakNode = document()->createHTMLElement("BR"); 01732 // assert(exceptionCode == 0); 01733 insertNodeAfter(extraBreakNode, nodeToInsert); 01734 setEndingSelection(Position(extraBreakNode, 0)); 01735 } else if (atStart) { 01736 #ifdef DEBUG_COMMANDS 01737 kDebug(6200) << "input newline case 2"; 01738 #endif 01739 // Insert node, but place the caret into index 0 of the downstream 01740 // position. This will make the caret appear after the break, and as we know 01741 // there is content at that location, this is OK. 01742 insertNodeBeforePosition(nodeToInsert, pos); 01743 setEndingSelection(Position(pos.node(), 0)); 01744 } else { 01745 // Split a text node 01746 // FIXME it's possible that we create empty text node now if we're at the end of text 01747 // maybe we should handle this case specially and not create it 01748 #ifdef DEBUG_COMMANDS 01749 kDebug(6200) << "input newline case 3"; 01750 #endif 01751 assert(pos.node()->isTextNode()); 01752 TextImpl *textNode = static_cast<TextImpl *>(pos.node()); 01753 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode)); 01754 deleteText(textNode, 0, selection.start().offset()); 01755 insertNodeBefore(textBeforeNode, textNode); 01756 insertNodeBefore(nodeToInsert, textNode); 01757 setEndingSelection(Position(textNode, 0)); 01758 } 01759 } 01760 01761 //------------------------------------------------------------------------------------------ 01762 // InputTextCommandImpl 01763 01764 InputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document) 01765 : CompositeEditCommandImpl(document), m_charactersAdded(0) 01766 { 01767 } 01768 01769 InputTextCommandImpl::~InputTextCommandImpl() 01770 { 01771 } 01772 01773 void InputTextCommandImpl::doApply() 01774 { 01775 } 01776 01777 void InputTextCommandImpl::input(const DOMString &text) 01778 { 01779 execute(text); 01780 } 01781 01782 void InputTextCommandImpl::deleteCharacter() 01783 { 01784 assert(state() == Applied); 01785 01786 Selection selection = endingSelection(); 01787 01788 if (!selection.start().node()->isTextNode()) 01789 return; 01790 01791 int exceptionCode = 0; 01792 int offset = selection.start().offset() - 1; 01793 if (offset >= selection.start().node()->caretMinOffset()) { 01794 TextImpl *textNode = static_cast<TextImpl *>(selection.start().node()); 01795 textNode->deleteData(offset, 1, exceptionCode); 01796 assert(exceptionCode == 0); 01797 selection = Selection(Position(textNode, offset)); 01798 setEndingSelection(selection); 01799 m_charactersAdded--; 01800 } 01801 } 01802 01803 Position InputTextCommandImpl::prepareForTextInsertion(bool adjustDownstream) 01804 { 01805 // Prepare for text input by looking at the current position. 01806 // It may be necessary to insert a text node to receive characters. 01807 Selection selection = endingSelection(); 01808 assert(selection.state() == Selection::CARET); 01809 01810 #ifdef DEBUG_COMMANDS 01811 kDebug() << "[prepare selection]" << selection << endl; 01812 #endif 01813 01814 Position pos = selection.start(); 01815 if (adjustDownstream) 01816 pos = pos.equivalentDownstreamPosition(); 01817 else 01818 pos = pos.equivalentUpstreamPosition(); 01819 01820 #ifdef DEBUG_COMMANDS 01821 kDebug() << "[prepare position]" << pos << endl; 01822 #endif 01823 01824 if (!pos.node()->isTextNode()) { 01825 NodeImpl *textNode = document()->createEditingTextNode(""); 01826 NodeImpl *nodeToInsert = textNode; 01827 if (document()->part()->editor()->typingStyle()) { 01828 int exceptionCode = 0; 01829 ElementImpl *styleElement = createTypingStyleElement(); 01830 styleElement->appendChild(textNode, exceptionCode); 01831 assert(exceptionCode == 0); 01832 nodeToInsert = styleElement; 01833 } 01834 01835 // Now insert the node in the right place 01836 if (pos.node()->isEditableBlock()) { 01837 kDebug(6200) << "prepareForTextInsertion case 1"; 01838 appendNode(pos.node(), nodeToInsert); 01839 } else if (pos.node()->id() == ID_BR && pos.offset() == 1) { 01840 kDebug(6200) << "prepareForTextInsertion case 2"; 01841 insertNodeAfter(nodeToInsert, pos.node()); 01842 } else if (pos.node()->caretMinOffset() == pos.offset()) { 01843 kDebug(6200) << "prepareForTextInsertion case 3"; 01844 insertNodeBefore(nodeToInsert, pos.node()); 01845 } else if (pos.node()->caretMaxOffset() == pos.offset()) { 01846 kDebug(6200) << "prepareForTextInsertion case 4"; 01847 insertNodeAfter(nodeToInsert, pos.node()); 01848 } else 01849 assert(false); 01850 01851 pos = Position(textNode, 0); 01852 } else { 01853 // Handle the case where there is a typing style. 01854 if (document()->part()->editor()->typingStyle()) { 01855 if (pos.node()->isTextNode() && pos.offset() > pos.node()->caretMinOffset() && pos.offset() < pos.node()->caretMaxOffset()) { 01856 // Need to split current text node in order to insert a span. 01857 TextImpl *text = static_cast<TextImpl *>(pos.node()); 01858 RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, pos.offset()); 01859 applyCommandToComposite(cmd); 01860 setEndingSelection(Position(cmd->node(), 0)); 01861 } 01862 01863 int exceptionCode = 0; 01864 TextImpl *editingTextNode = document()->createEditingTextNode(""); 01865 01866 ElementImpl *styleElement = createTypingStyleElement(); 01867 styleElement->appendChild(editingTextNode, exceptionCode); 01868 assert(exceptionCode == 0); 01869 01870 NodeImpl *node = endingSelection().start().node(); 01871 if (endingSelection().start().isLastRenderedPositionOnLine()) 01872 insertNodeAfter(styleElement, node); 01873 else 01874 insertNodeBefore(styleElement, node); 01875 pos = Position(editingTextNode, 0); 01876 } 01877 } 01878 return pos; 01879 } 01880 01881 void InputTextCommandImpl::execute(const DOMString &text) 01882 { 01883 #ifdef DEBUG_COMMANDS 01884 kDebug() << "[execute command]" << text << endl; 01885 #endif 01886 Selection selection = endingSelection(); 01887 #ifdef DEBUG_COMMANDS 01888 kDebug() << "[ending selection]" << selection << endl; 01889 #endif 01890 bool adjustDownstream = selection.start().isFirstRenderedPositionOnLine(); 01891 #ifdef DEBUG_COMMANDS 01892 kDebug() << "[adjust]" << adjustDownstream << endl; 01893 #endif 01894 01895 #ifdef DEBUG_COMMANDS 01896 printEnclosingBlockTree(selection.start().node()); 01897 #endif 01898 01899 // Delete the current selection, or collapse whitespace, as needed 01900 if (selection.state() == Selection::RANGE) 01901 deleteSelection(); 01902 else 01903 deleteCollapsibleWhitespace(); 01904 01905 #ifdef DEBUG_COMMANDS 01906 kDebug() << "[after collapsible whitespace deletion]" << endl; 01907 printEnclosingBlockTree(selection.start().node()); 01908 #endif 01909 01910 // EDIT FIXME: Need to take typing style from upstream text, if any. 01911 01912 // Make sure the document is set up to receive text 01913 Position pos = prepareForTextInsertion(adjustDownstream); 01914 #ifdef DEBUG_COMMANDS 01915 kDebug() << "[after prepare]" << pos << endl; 01916 #endif 01917 01918 TextImpl *textNode = static_cast<TextImpl *>(pos.node()); 01919 long offset = pos.offset(); 01920 01921 #ifdef DEBUG_COMMANDS 01922 kDebug() << "[insert at]" << textNode << offset << endl; 01923 #endif 01924 01925 // This is a temporary implementation for inserting adjoining spaces 01926 // into a document. We are working on a CSS-related whitespace solution 01927 // that will replace this some day. 01928 if (isWS(text)) 01929 insertSpace(textNode, offset); 01930 else { 01931 const DOMString &existingText = textNode->data(); 01932 if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isWS(existingText[offset - 2])) { 01933 // DOM looks like this: 01934 // character nbsp caret 01935 // As we are about to insert a non-whitespace character at the caret 01936 // convert the nbsp to a regular space. 01937 // EDIT FIXME: This needs to be improved some day to convert back only 01938 // those nbsp's added by the editor to make rendering come out right. 01939 replaceText(textNode, offset - 1, 1, " "); 01940 } 01941 insertText(textNode, offset, text); 01942 } 01943 setEndingSelection(Position(textNode, offset + text.length())); 01944 m_charactersAdded += text.length(); 01945 } 01946 01947 void InputTextCommandImpl::insertSpace(TextImpl *textNode, unsigned long offset) 01948 { 01949 assert(textNode); 01950 01951 DOMString text(textNode->data()); 01952 01953 // count up all spaces and newlines in front of the caret 01954 // delete all collapsed ones 01955 // this will work out OK since the offset we have been passed has been upstream-ized 01956 int count = 0; 01957 for (unsigned int i = offset; i < text.length(); i++) { 01958 if (isWS(text[i])) 01959 count++; 01960 else 01961 break; 01962 } 01963 if (count > 0) { 01964 // By checking the character at the downstream position, we can 01965 // check if there is a rendered WS at the caret 01966 Position pos(textNode, offset); 01967 Position downstream = pos.equivalentDownstreamPosition(); 01968 if (downstream.offset() < (long)text.length() && isWS(text[downstream.offset()])) 01969 count--; // leave this WS in 01970 if (count > 0) 01971 deleteText(textNode, offset, count); 01972 } 01973 01974 if (offset > 0 && offset <= text.length() - 1 && !isWS(text[offset]) && !isWS(text[offset - 1])) { 01975 // insert a "regular" space 01976 insertText(textNode, offset, " "); 01977 return; 01978 } 01979 01980 if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) { 01981 // DOM looks like this: 01982 // nbsp nbsp caret 01983 // insert a space between the two nbsps 01984 insertText(textNode, offset - 1, " "); 01985 return; 01986 } 01987 01988 // insert an nbsp 01989 insertText(textNode, offset, nonBreakingSpaceString()); 01990 } 01991 01992 //------------------------------------------------------------------------------------------ 01993 // InsertNodeBeforeCommandImpl 01994 01995 InsertNodeBeforeCommandImpl::InsertNodeBeforeCommandImpl(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild) 01996 : EditCommandImpl(document), m_insertChild(insertChild), m_refChild(refChild) 01997 { 01998 assert(m_insertChild); 01999 m_insertChild->ref(); 02000 02001 assert(m_refChild); 02002 m_refChild->ref(); 02003 } 02004 02005 InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl() 02006 { 02007 if (m_insertChild) 02008 m_insertChild->deref(); 02009 if (m_refChild) 02010 m_refChild->deref(); 02011 } 02012 02013 void InsertNodeBeforeCommandImpl::doApply() 02014 { 02015 assert(m_insertChild); 02016 assert(m_refChild); 02017 assert(m_refChild->parentNode()); 02018 02019 int exceptionCode = 0; 02020 m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode); 02021 assert(exceptionCode == 0); 02022 } 02023 02024 void InsertNodeBeforeCommandImpl::doUnapply() 02025 { 02026 assert(m_insertChild); 02027 assert(m_refChild); 02028 assert(m_refChild->parentNode()); 02029 02030 int exceptionCode = 0; 02031 m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode); 02032 assert(exceptionCode == 0); 02033 } 02034 02035 //------------------------------------------------------------------------------------------ 02036 // InsertTextCommandImpl 02037 02038 InsertTextCommandImpl::InsertTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text) 02039 : EditCommandImpl(document), m_node(node), m_offset(offset) 02040 { 02041 assert(m_node); 02042 assert(m_offset >= 0); 02043 assert(text.length() > 0); 02044 02045 m_node->ref(); 02046 m_text = text.copy(); // make a copy to ensure that the string never changes 02047 } 02048 02049 InsertTextCommandImpl::~InsertTextCommandImpl() 02050 { 02051 if (m_node) 02052 m_node->deref(); 02053 } 02054 02055 void InsertTextCommandImpl::doApply() 02056 { 02057 assert(m_node); 02058 assert(!m_text.isEmpty()); 02059 02060 int exceptionCode = 0; 02061 m_node->insertData(m_offset, m_text, exceptionCode); 02062 assert(exceptionCode == 0); 02063 } 02064 02065 void InsertTextCommandImpl::doUnapply() 02066 { 02067 assert(m_node); 02068 assert(!m_text.isEmpty()); 02069 02070 int exceptionCode = 0; 02071 m_node->deleteData(m_offset, m_text.length(), exceptionCode); 02072 assert(exceptionCode == 0); 02073 } 02074 02075 //------------------------------------------------------------------------------------------ 02076 // JoinTextNodesCommandImpl 02077 02078 JoinTextNodesCommandImpl::JoinTextNodesCommandImpl(DocumentImpl *document, TextImpl *text1, TextImpl *text2) 02079 : EditCommandImpl(document), m_text1(text1), m_text2(text2) 02080 { 02081 assert(m_text1); 02082 assert(m_text2); 02083 assert(m_text1->nextSibling() == m_text2); 02084 assert(m_text1->length() > 0); 02085 assert(m_text2->length() > 0); 02086 02087 m_text1->ref(); 02088 m_text2->ref(); 02089 } 02090 02091 JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl() 02092 { 02093 if (m_text1) 02094 m_text1->deref(); 02095 if (m_text2) 02096 m_text2->deref(); 02097 } 02098 02099 void JoinTextNodesCommandImpl::doApply() 02100 { 02101 assert(m_text1); 02102 assert(m_text2); 02103 assert(m_text1->nextSibling() == m_text2); 02104 02105 int exceptionCode = 0; 02106 m_text2->insertData(0, m_text1->data(), exceptionCode); 02107 assert(exceptionCode == 0); 02108 02109 m_text2->parentNode()->removeChild(m_text1, exceptionCode); 02110 assert(exceptionCode == 0); 02111 02112 m_offset = m_text1->length(); 02113 } 02114 02115 void JoinTextNodesCommandImpl::doUnapply() 02116 { 02117 assert(m_text2); 02118 assert(m_offset > 0); 02119 02120 int exceptionCode = 0; 02121 02122 m_text2->deleteData(0, m_offset, exceptionCode); 02123 assert(exceptionCode == 0); 02124 02125 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode); 02126 assert(exceptionCode == 0); 02127 02128 assert(m_text2->previousSibling()->isTextNode()); 02129 assert(m_text2->previousSibling() == m_text1); 02130 } 02131 02132 //------------------------------------------------------------------------------------------ 02133 // ReplaceSelectionCommandImpl 02134 02135 ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement) 02136 : CompositeEditCommandImpl(document), m_fragment(fragment), m_selectReplacement(selectReplacement) 02137 { 02138 } 02139 02140 ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl() 02141 { 02142 } 02143 02144 void ReplaceSelectionCommandImpl::doApply() 02145 { 02146 NodeImpl *firstChild = m_fragment->firstChild(); 02147 NodeImpl *lastChild = m_fragment->lastChild(); 02148 02149 Selection selection = endingSelection(); 02150 02151 // Delete the current selection, or collapse whitespace, as needed 02152 if (selection.state() == Selection::RANGE) 02153 deleteSelection(); 02154 else 02155 deleteCollapsibleWhitespace(); 02156 02157 selection = endingSelection(); 02158 assert(!selection.isEmpty()); 02159 02160 if (!firstChild) { 02161 // Pasting something that didn't parse or was empty. 02162 assert(!lastChild); 02163 } else if (firstChild == lastChild && firstChild->isTextNode()) { 02164 // Simple text paste. Treat as if the text were typed. 02165 Position base = selection.base(); 02166 inputText(static_cast<TextImpl *>(firstChild)->data()); 02167 if (m_selectReplacement) { 02168 setEndingSelection(Selection(base, endingSelection().extent())); 02169 } 02170 } 02171 else { 02172 // HTML fragment paste. 02173 NodeImpl *beforeNode = firstChild; 02174 NodeImpl *node = firstChild->nextSibling(); 02175 02176 insertNodeAt(firstChild, selection.start().node(), selection.start().offset()); 02177 02178 // Insert the nodes from the fragment 02179 while (node) { 02180 NodeImpl *next = node->nextSibling(); 02181 insertNodeAfter(node, beforeNode); 02182 beforeNode = node; 02183 node = next; 02184 } 02185 assert(beforeNode); 02186 02187 // Find the last leaf. 02188 NodeImpl *lastLeaf = lastChild; 02189 while (1) { 02190 NodeImpl *nextChild = lastLeaf->lastChild(); 02191 if (!nextChild) 02192 break; 02193 lastLeaf = nextChild; 02194 } 02195 02196 if (m_selectReplacement) { 02197 // Find the first leaf. 02198 NodeImpl *firstLeaf = firstChild; 02199 while (1) { 02200 NodeImpl *nextChild = firstLeaf->firstChild(); 02201 if (!nextChild) 02202 break; 02203 firstLeaf = nextChild; 02204 } 02205 // Select what was inserted. 02206 setEndingSelection(Selection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset()))); 02207 } else { 02208 // Place the cursor after what was inserted. 02209 setEndingSelection(Position(lastLeaf, lastLeaf->caretMaxOffset())); 02210 } 02211 } 02212 } 02213 02214 //------------------------------------------------------------------------------------------ 02215 // MoveSelectionCommandImpl 02216 02217 MoveSelectionCommandImpl::MoveSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position) 02218 : CompositeEditCommandImpl(document), m_fragment(fragment), m_position(position) 02219 { 02220 } 02221 02222 MoveSelectionCommandImpl::~MoveSelectionCommandImpl() 02223 { 02224 } 02225 02226 void MoveSelectionCommandImpl::doApply() 02227 { 02228 Selection selection = endingSelection(); 02229 assert(selection.state() == Selection::RANGE); 02230 02231 // Update the position otherwise it may become invalid after the selection is deleted. 02232 NodeImpl *positionNode = m_position.node(); 02233 long positionOffset = m_position.offset(); 02234 Position selectionEnd = selection.end(); 02235 long selectionEndOffset = selectionEnd.offset(); 02236 if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) { 02237 positionOffset -= selectionEndOffset; 02238 Position selectionStart = selection.start(); 02239 if (selectionStart.node() == positionNode) { 02240 positionOffset += selectionStart.offset(); 02241 } 02242 } 02243 02244 deleteSelection(); 02245 02246 setEndingSelection(Position(positionNode, positionOffset)); 02247 RefPtr<ReplaceSelectionCommandImpl> cmd = new ReplaceSelectionCommandImpl(document(), m_fragment, true); 02248 applyCommandToComposite(cmd); 02249 } 02250 02251 //------------------------------------------------------------------------------------------ 02252 // RemoveCSSPropertyCommandImpl 02253 02254 RemoveCSSPropertyCommandImpl::RemoveCSSPropertyCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property) 02255 : EditCommandImpl(document), m_decl(decl), m_property(property), m_important(false) 02256 { 02257 assert(m_decl); 02258 m_decl->ref(); 02259 } 02260 02261 RemoveCSSPropertyCommandImpl::~RemoveCSSPropertyCommandImpl() 02262 { 02263 assert(m_decl); 02264 m_decl->deref(); 02265 } 02266 02267 void RemoveCSSPropertyCommandImpl::doApply() 02268 { 02269 assert(m_decl); 02270 02271 m_oldValue = m_decl->getPropertyValue(m_property); 02272 assert(!m_oldValue.isNull()); 02273 02274 m_important = m_decl->getPropertyPriority(m_property); 02275 m_decl->removeProperty(m_property); 02276 } 02277 02278 void RemoveCSSPropertyCommandImpl::doUnapply() 02279 { 02280 assert(m_decl); 02281 assert(!m_oldValue.isNull()); 02282 02283 m_decl->setProperty(m_property, m_oldValue, m_important); 02284 } 02285 02286 //------------------------------------------------------------------------------------------ 02287 // RemoveNodeAttributeCommandImpl 02288 02289 RemoveNodeAttributeCommandImpl::RemoveNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute) 02290 : EditCommandImpl(document), m_element(element), m_attribute(attribute) 02291 { 02292 assert(m_element); 02293 m_element->ref(); 02294 } 02295 02296 RemoveNodeAttributeCommandImpl::~RemoveNodeAttributeCommandImpl() 02297 { 02298 assert(m_element); 02299 m_element->deref(); 02300 } 02301 02302 void RemoveNodeAttributeCommandImpl::doApply() 02303 { 02304 assert(m_element); 02305 02306 m_oldValue = m_element->getAttribute(m_attribute); 02307 assert(!m_oldValue.isNull()); 02308 02309 int exceptionCode = 0; 02310 m_element->removeAttribute(m_attribute, exceptionCode); 02311 assert(exceptionCode == 0); 02312 } 02313 02314 void RemoveNodeAttributeCommandImpl::doUnapply() 02315 { 02316 assert(m_element); 02317 assert(!m_oldValue.isNull()); 02318 02319 // int exceptionCode = 0; 02320 m_element->setAttribute(m_attribute, m_oldValue.implementation()); 02321 // assert(exceptionCode == 0); 02322 } 02323 02324 //------------------------------------------------------------------------------------------ 02325 // RemoveNodeCommandImpl 02326 02327 RemoveNodeCommandImpl::RemoveNodeCommandImpl(DocumentImpl *document, NodeImpl *removeChild) 02328 : EditCommandImpl(document), m_parent(0), m_removeChild(removeChild), m_refChild(0) 02329 { 02330 assert(m_removeChild); 02331 m_removeChild->ref(); 02332 02333 m_parent = m_removeChild->parentNode(); 02334 assert(m_parent); 02335 m_parent->ref(); 02336 02337 NodeListImpl *children = m_parent->childNodes(); 02338 for (int i = children->length(); i >= 0; i--) { 02339 NodeImpl *node = children->item(i); 02340 if (node == m_removeChild) 02341 break; 02342 m_refChild = node; 02343 } 02344 02345 if (m_refChild) 02346 m_refChild->ref(); 02347 } 02348 02349 RemoveNodeCommandImpl::~RemoveNodeCommandImpl() 02350 { 02351 if (m_parent) 02352 m_parent->deref(); 02353 if (m_removeChild) 02354 m_removeChild->deref(); 02355 if (m_refChild) 02356 m_refChild->deref(); 02357 } 02358 02359 void RemoveNodeCommandImpl::doApply() 02360 { 02361 assert(m_parent); 02362 assert(m_removeChild); 02363 02364 int exceptionCode = 0; 02365 m_parent->removeChild(m_removeChild, exceptionCode); 02366 assert(exceptionCode == 0); 02367 } 02368 02369 void RemoveNodeCommandImpl::doUnapply() 02370 { 02371 assert(m_parent); 02372 assert(m_removeChild); 02373 02374 int exceptionCode = 0; 02375 if (m_refChild) 02376 m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode); 02377 else 02378 m_parent->appendChild(m_removeChild, exceptionCode); 02379 assert(exceptionCode == 0); 02380 } 02381 02382 //------------------------------------------------------------------------------------------ 02383 // RemoveNodeAndPruneCommandImpl 02384 02385 RemoveNodeAndPruneCommandImpl::RemoveNodeAndPruneCommandImpl(DocumentImpl *document, NodeImpl *pruneNode, NodeImpl *stopNode) 02386 : CompositeEditCommandImpl(document), m_pruneNode(pruneNode), m_stopNode(stopNode) 02387 { 02388 assert(m_pruneNode); 02389 m_pruneNode->ref(); 02390 if (m_stopNode) 02391 m_stopNode->ref(); 02392 } 02393 02394 RemoveNodeAndPruneCommandImpl::~RemoveNodeAndPruneCommandImpl() 02395 { 02396 m_pruneNode->deref(); 02397 if (m_stopNode) 02398 m_stopNode->deref(); 02399 } 02400 02401 void RemoveNodeAndPruneCommandImpl::doApply() 02402 { 02403 NodeImpl *editableBlock = m_pruneNode->enclosingBlockFlowElement(); 02404 NodeImpl *pruneNode = m_pruneNode; 02405 NodeImpl *node = pruneNode->traversePreviousNode(); 02406 removeNode(pruneNode); 02407 while (1) { 02408 if (node == m_stopNode || editableBlock != node->enclosingBlockFlowElement() || !shouldPruneNode(node)) 02409 break; 02410 pruneNode = node; 02411 node = node->traversePreviousNode(); 02412 removeNode(pruneNode); 02413 } 02414 } 02415 02416 //------------------------------------------------------------------------------------------ 02417 // RemoveNodePreservingChildrenCommandImpl 02418 02419 RemoveNodePreservingChildrenCommandImpl::RemoveNodePreservingChildrenCommandImpl(DocumentImpl *document, NodeImpl *node) 02420 : CompositeEditCommandImpl(document), m_node(node) 02421 { 02422 assert(m_node); 02423 m_node->ref(); 02424 } 02425 02426 RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl() 02427 { 02428 if (m_node) 02429 m_node->deref(); 02430 } 02431 02432 void RemoveNodePreservingChildrenCommandImpl::doApply() 02433 { 02434 NodeListImpl *children = node()->childNodes(); 02435 int length = children->length(); 02436 for (int i = 0; i < length; i++) { 02437 NodeImpl *child = children->item(0); 02438 removeNode(child); 02439 insertNodeBefore(child, node()); 02440 } 02441 removeNode(node()); 02442 } 02443 02444 //------------------------------------------------------------------------------------------ 02445 // SetNodeAttributeCommandImpl 02446 02447 SetNodeAttributeCommandImpl::SetNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value) 02448 : EditCommandImpl(document), m_element(element), m_attribute(attribute), m_value(value) 02449 { 02450 assert(m_element); 02451 m_element->ref(); 02452 assert(!m_value.isNull()); 02453 } 02454 02455 SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl() 02456 { 02457 if (m_element) 02458 m_element->deref(); 02459 } 02460 02461 void SetNodeAttributeCommandImpl::doApply() 02462 { 02463 assert(m_element); 02464 assert(!m_value.isNull()); 02465 02466 // int exceptionCode = 0; 02467 m_oldValue = m_element->getAttribute(m_attribute); 02468 m_element->setAttribute(m_attribute, m_value.implementation()); 02469 // assert(exceptionCode == 0); 02470 } 02471 02472 void SetNodeAttributeCommandImpl::doUnapply() 02473 { 02474 assert(m_element); 02475 assert(!m_oldValue.isNull()); 02476 02477 // int exceptionCode = 0; 02478 m_element->setAttribute(m_attribute, m_oldValue.implementation()); 02479 // assert(exceptionCode == 0); 02480 } 02481 02482 //------------------------------------------------------------------------------------------ 02483 // SplitTextNodeCommandImpl 02484 02485 SplitTextNodeCommandImpl::SplitTextNodeCommandImpl(DocumentImpl *document, TextImpl *text, long offset) 02486 : EditCommandImpl(document), m_text1(0), m_text2(text), m_offset(offset) 02487 { 02488 assert(m_text2); 02489 assert(m_text2->length() > 0); 02490 02491 m_text2->ref(); 02492 } 02493 02494 SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl() 02495 { 02496 if (m_text1) 02497 m_text1->deref(); 02498 if (m_text2) 02499 m_text2->deref(); 02500 } 02501 02502 void SplitTextNodeCommandImpl::doApply() 02503 { 02504 assert(m_text2); 02505 assert(m_offset > 0); 02506 02507 int exceptionCode = 0; 02508 02509 // EDIT FIXME: This should use better smarts for figuring out which portion 02510 // of the split to copy (based on their comparative sizes). We should also 02511 // just use the DOM's splitText function. 02512 02513 if (!m_text1) { 02514 // create only if needed. 02515 // if reapplying, this object will already exist. 02516 m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode)); 02517 assert(exceptionCode == 0); 02518 assert(m_text1); 02519 m_text1->ref(); 02520 } 02521 02522 m_text2->deleteData(0, m_offset, exceptionCode); 02523 assert(exceptionCode == 0); 02524 02525 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode); 02526 assert(exceptionCode == 0); 02527 02528 assert(m_text2->previousSibling()->isTextNode()); 02529 assert(m_text2->previousSibling() == m_text1); 02530 } 02531 02532 void SplitTextNodeCommandImpl::doUnapply() 02533 { 02534 assert(m_text1); 02535 assert(m_text2); 02536 02537 assert(m_text1->nextSibling() == m_text2); 02538 02539 int exceptionCode = 0; 02540 m_text2->insertData(0, m_text1->data(), exceptionCode); 02541 assert(exceptionCode == 0); 02542 02543 m_text2->parentNode()->removeChild(m_text1, exceptionCode); 02544 assert(exceptionCode == 0); 02545 02546 m_offset = m_text1->length(); 02547 } 02548 02549 //------------------------------------------------------------------------------------------ 02550 // TypingCommandImpl 02551 02552 TypingCommandImpl::TypingCommandImpl(DocumentImpl *document) 02553 : CompositeEditCommandImpl(document), m_openForMoreTyping(true) 02554 { 02555 } 02556 02557 TypingCommandImpl::~TypingCommandImpl() 02558 { 02559 } 02560 02561 void TypingCommandImpl::doApply() 02562 { 02563 } 02564 02565 void TypingCommandImpl::typingAddedToOpenCommand() 02566 { 02567 assert(document()); 02568 assert(document()->part()); 02569 document()->part()->editor()->appliedEditing(this); 02570 } 02571 02572 void TypingCommandImpl::insertText(const DOMString &text) 02573 { 02574 if (document()->part()->editor()->typingStyle() || m_cmds.count() == 0) { 02575 RefPtr<InputTextCommandImpl> cmd = new InputTextCommandImpl(document()); 02576 applyCommandToComposite(cmd); 02577 cmd->input(text); 02578 } else { 02579 EditCommandImpl *lastCommand = m_cmds.last().get(); 02580 if (lastCommand->isInputTextCommand()) { 02581 static_cast<InputTextCommandImpl*>(lastCommand)->input(text); 02582 } else { 02583 RefPtr<InputTextCommandImpl> cmd = new InputTextCommandImpl(document()); 02584 applyCommandToComposite(cmd); 02585 cmd->input(text); 02586 } 02587 } 02588 typingAddedToOpenCommand(); 02589 } 02590 02591 void TypingCommandImpl::insertNewline() 02592 { 02593 RefPtr<InputNewlineCommandImpl> cmd = new InputNewlineCommandImpl(document()); 02594 applyCommandToComposite(cmd); 02595 typingAddedToOpenCommand(); 02596 } 02597 02598 void TypingCommandImpl::issueCommandForDeleteKey() 02599 { 02600 Selection selectionToDelete = endingSelection(); 02601 assert(selectionToDelete.state() != Selection::NONE); 02602 02603 #ifdef DEBUG_COMMANDS 02604 kDebug() << "[selection]" << selectionToDelete << endl; 02605 #endif 02606 if (selectionToDelete.state() == Selection::CARET) { 02607 #ifdef DEBUG_COMMANDS 02608 kDebug() << "[caret selection]" << endl; 02609 #endif 02610 Position pos(selectionToDelete.start()); 02611 if (pos.inFirstEditableInRootEditableElement() && pos.offset() <= pos.node()->caretMinOffset()) { 02612 // we're at the start of a root editable block...do nothing 02613 return; 02614 } 02615 selectionToDelete = Selection(pos.previousCharacterPosition(), pos); 02616 #ifdef DEBUG_COMMANDS 02617 kDebug() << "[modified selection]" << selectionToDelete << endl; 02618 #endif 02619 } 02620 deleteSelection(selectionToDelete); 02621 typingAddedToOpenCommand(); 02622 } 02623 02624 void TypingCommandImpl::deleteKeyPressed() 02625 { 02626 // EDIT FIXME: The ifdef'ed out code below should be re-enabled. 02627 // In order for this to happen, the deleteCharacter case 02628 // needs work. Specifically, the caret-positioning code 02629 // and whitespace-handling code in DeleteSelectionCommandImpl::doApply() 02630 // needs to be factored out so it can be used again here. 02631 // Until that work is done, issueCommandForDeleteKey() does the 02632 // right thing, but less efficiently and with the cost of more 02633 // objects. 02634 issueCommandForDeleteKey(); 02635 #if 0 02636 if (m_cmds.count() == 0) { 02637 issueCommandForDeleteKey(); 02638 } 02639 else { 02640 EditCommand lastCommand = m_cmds.last(); 02641 if (lastCommand.commandID() == InputTextCommandID) { 02642 InputTextCommand cmd = static_cast<InputTextCommand &>(lastCommand); 02643 cmd.deleteCharacter(); 02644 if (cmd.charactersAdded() == 0) { 02645 removeCommand(cmd); 02646 } 02647 } 02648 else if (lastCommand.commandID() == InputNewlineCommandID) { 02649 lastCommand.unapply(); 02650 removeCommand(lastCommand); 02651 } 02652 else { 02653 issueCommandForDeleteKey(); 02654 } 02655 } 02656 #endif 02657 } 02658 02659 void TypingCommandImpl::removeCommand(const PassRefPtr<EditCommandImpl> cmd) 02660 { 02661 // NOTE: If the passed-in command is the last command in the 02662 // composite, we could remove all traces of this typing command 02663 // from the system, including the undo chain. Other editors do 02664 // not do this, but we could. 02665 02666 m_cmds.removeAll(cmd); 02667 if (m_cmds.count() == 0) 02668 setEndingSelection(startingSelection()); 02669 else 02670 setEndingSelection(m_cmds.last()->endingSelection()); 02671 } 02672 02673 static bool isOpenForMoreTypingCommand(const EditCommandImpl *command) 02674 { 02675 return command && command->isTypingCommand() && 02676 static_cast<const TypingCommandImpl*>(command)->openForMoreTyping(); 02677 } 02678 02679 void TypingCommandImpl::deleteKeyPressed0(DocumentImpl *document) 02680 { 02681 //Editor *editor = document->part()->editor(); 02682 // FIXME reenable after properly modify selection of the lastEditCommand 02683 // if (isOpenForMoreTypingCommand(lastEditCommand)) { 02684 // static_cast<TypingCommand &>(lastEditCommand).deleteKeyPressed(); 02685 // } else { 02686 RefPtr<TypingCommandImpl> command = new TypingCommandImpl(document); 02687 command->apply(); 02688 command->deleteKeyPressed(); 02689 // } 02690 } 02691 02692 void TypingCommandImpl::insertNewline0(DocumentImpl *document) 02693 { 02694 assert(document); 02695 Editor *ed = document->part()->editor(); 02696 assert(ed); 02697 EditCommandImpl *lastEditCommand = ed->lastEditCommand().get(); 02698 if (isOpenForMoreTypingCommand(lastEditCommand)) { 02699 static_cast<TypingCommandImpl*>(lastEditCommand)->insertNewline(); 02700 } else { 02701 RefPtr<TypingCommandImpl> command = new TypingCommandImpl(document); 02702 command->apply(); 02703 command->insertNewline(); 02704 } 02705 } 02706 02707 void TypingCommandImpl::insertText0(DocumentImpl *document, const DOMString &text) 02708 { 02709 #ifdef DEBUG_COMMANDS 02710 kDebug() << "[insert text]" << text << endl; 02711 #endif 02712 assert(document); 02713 Editor *ed = document->part()->editor(); 02714 assert(ed); 02715 EditCommandImpl *lastEditCommand = ed->lastEditCommand().get(); 02716 if (isOpenForMoreTypingCommand(lastEditCommand)) { 02717 static_cast<TypingCommandImpl*>(lastEditCommand)->insertText(text); 02718 } else { 02719 RefPtr<TypingCommandImpl> command = new TypingCommandImpl(document); 02720 command->apply(); 02721 command->insertText(text); 02722 } 02723 } 02724 02725 02726 //------------------------------------------------------------------------------------------ 02727 // InsertListCommandImpl 02728 02729 InsertListCommandImpl::InsertListCommandImpl(DocumentImpl *document, Type type) 02730 : CompositeEditCommandImpl(document), m_listType(type) 02731 { 02732 } 02733 02734 InsertListCommandImpl::~InsertListCommandImpl() 02735 { 02736 } 02737 02738 void InsertListCommandImpl::doApply() 02739 { 02740 #ifdef DEBUG_COMMANDS 02741 kDebug() << "[make current selection/paragraph a list]" << endingSelection() << endl; 02742 #endif 02743 Position start = endingSelection().start(); 02744 Position end = endingSelection().end(); 02745 ElementImpl *startBlock = start.node()->enclosingBlockFlowElement(); 02746 ElementImpl *endBlock = end.node()->enclosingBlockFlowElement(); 02747 #ifdef DEBUG_COMMANDS 02748 kDebug() << "[start:end blocks]" << startBlock << endBlock << endl; 02749 printEnclosingBlockTree(start.node()); 02750 #endif 02751 if (startBlock == endBlock) { 02752 if (startBlock->id() == ID_LI) { 02753 // we already have a list item, remove it then 02754 #ifdef DEBUG_COMMANDS 02755 kDebug() << "[remove list item]" << endl; 02756 #endif 02757 NodeImpl *listBlock = startBlock->parent(); // it's either <ol> or <ul> 02758 // we need to properly split or even remove the list leaving 2 lists: 02759 // [listBlock->firstChild(), startBlock) and (startBlock, listBlock->lastChild()] 02760 if (listBlock->firstChild() == listBlock->lastChild() && listBlock->firstChild() == startBlock) { 02761 // get rid of list completely 02762 #ifdef DEBUG_COMMANDS 02763 kDebug() << "[remove list completely]" << endl; 02764 #endif 02765 removeNodePreservingChildren(listBlock); 02766 removeNodePreservingChildren(startBlock); 02767 } else if (!startBlock->previousSibling()) { 02768 // move nodes from this list item before the list 02769 NodeImpl *nextSibling; 02770 for (NodeImpl *node = startBlock->firstChild(); node; node = nextSibling) { 02771 nextSibling = node->nextSibling(); 02772 removeNode(node); 02773 insertNodeBefore(node, listBlock); 02774 } 02775 removeNode(startBlock); 02776 } else if (!startBlock->nextSibling()) { 02777 // move nodes from this list item after the list 02778 NodeImpl *nextSibling; 02779 for (NodeImpl *node = startBlock->lastChild(); node; node = nextSibling) { 02780 nextSibling = node->previousSibling(); 02781 removeNode(node); 02782 insertNodeAfter(node, listBlock); 02783 } 02784 removeNode(startBlock); 02785 } else { 02786 // split list into 2 and nodes from this list item goes between lists 02787 WTF::PassRefPtr<NodeImpl> newListBlock = listBlock->cloneNode(false); 02788 insertNodeAfter(newListBlock.get(), listBlock); 02789 NodeImpl *node, *nextSibling; 02790 for (node = startBlock->nextSibling(); node; node = nextSibling) { 02791 nextSibling = node->nextSibling(); 02792 removeNode(node); 02793 appendNode(newListBlock.get(), node); 02794 } 02795 for (node = startBlock->firstChild(); node; node = nextSibling) { 02796 nextSibling = node->nextSibling(); 02797 removeNode(node); 02798 insertNodeBefore(node, newListBlock.get()); 02799 } 02800 removeNode(startBlock); 02801 } 02802 } else { 02803 ElementImpl *ol = document()->createHTMLElement(m_listType == OrderedList ? "OL" : "UL"); 02804 ElementImpl *li = document()->createHTMLElement("LI"); 02805 appendNode(ol, li); 02806 NodeImpl *nextNode; 02807 for (NodeImpl *node = startBlock->firstChild(); node; node = nextNode) { 02808 #ifdef DEBUG_COMMANDS 02809 kDebug() << "[reattach node]" << node << endl; 02810 #endif 02811 nextNode = node->nextSibling(); 02812 removeNode(node); 02813 appendNode(li, node); 02814 } 02815 appendNode(startBlock, ol); 02816 } 02817 } else { 02818 #ifdef DEBUG_COMMANDS 02819 kDebug() << "[different blocks are not supported yet]" << endl; 02820 #endif 02821 } 02822 } 02823 02824 void InsertListCommandImpl::insertList(DocumentImpl *document, Type type) 02825 { 02826 RefPtr<InsertListCommandImpl> insertCommand = new InsertListCommandImpl(document, type); 02827 insertCommand->apply(); 02828 } 02829 02830 //------------------------------------------------------------------------------------------ 02831 02832 //------------------------------------------------------------------------------------------ 02833 // IndentOutdentCommandImpl 02834 02835 IndentOutdentCommandImpl::IndentOutdentCommandImpl(DocumentImpl *document, Type type) 02836 : CompositeEditCommandImpl(document), m_commandType(type) 02837 { 02838 } 02839 02840 IndentOutdentCommandImpl::~IndentOutdentCommandImpl() 02841 { 02842 } 02843 02844 void IndentOutdentCommandImpl::indent() 02845 { 02846 Selection selection = endingSelection(); 02847 #ifdef DEBUG_COMMANDS 02848 kDebug() << "[indent selection]" << selection << endl; 02849 #endif 02850 NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement(); 02851 NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement(); 02852 02853 if (startBlock == endBlock) { 02854 // check if selection is the list, but not fully covered 02855 if (startBlock->id() == ID_LI && (startBlock->previousSibling() || startBlock->nextSibling())) { 02856 #ifdef DEBUG_COMMANDS 02857 kDebug() << "[modify list]" << endl; 02858 #endif 02859 RefPtr<NodeImpl> newList = startBlock->parent()->cloneNode(false); 02860 insertNodeAfter(newList.get(), startBlock); 02861 removeNode(startBlock); 02862 appendNode(newList.get(), startBlock); 02863 } else { 02864 NodeImpl *blockquoteElement = document()->createHTMLElement("blockquote"); 02865 if (startBlock->id() == ID_LI) { 02866 startBlock = startBlock->parent(); 02867 NodeImpl *parent = startBlock->parent(); 02868 removeNode(startBlock); 02869 appendNode(parent, blockquoteElement); 02870 appendNode(blockquoteElement, startBlock); 02871 } else { 02872 NodeImpl *parent = startBlock->parent(); 02873 removeNode(startBlock); 02874 appendNode(parent, blockquoteElement); 02875 appendNode(blockquoteElement, startBlock); 02876 } 02877 } 02878 } else { 02879 if (startBlock->id() == ID_LI && endBlock->id() == ID_LI && startBlock->parent() == endBlock->parent()) { 02880 #ifdef DEBUG_COMMANDS 02881 kDebug() << "[indent some items inside list]" << endl; 02882 #endif 02883 RefPtr<NodeImpl> nestedList = startBlock->parent()->cloneNode(false); 02884 insertNodeBefore(nestedList.get(), startBlock); 02885 NodeImpl *nextNode = 0; 02886 for (NodeImpl *node = startBlock;; node = nextNode) { 02887 nextNode = node->nextSibling(); 02888 removeNode(node); 02889 appendNode(nestedList.get(), node); 02890 if (node == endBlock) 02891 break; 02892 } 02893 } else { 02894 #ifdef DEBUG_COMMANDS 02895 kDebug() << "[blocks not from one list are not supported yet]" << endl; 02896 #endif 02897 } 02898 } 02899 } 02900 02901 static bool hasPreviousListItem(NodeImpl *node) 02902 { 02903 while (node) { 02904 node = node->previousSibling(); 02905 if (node && node->id() == ID_LI) 02906 return true; 02907 } 02908 return false; 02909 } 02910 02911 static bool hasNextListItem(NodeImpl *node) 02912 { 02913 while (node) { 02914 node = node->nextSibling(); 02915 if (node && node->id() == ID_LI) 02916 return true; 02917 } 02918 return false; 02919 } 02920 02921 void IndentOutdentCommandImpl::outdent() 02922 { 02923 Selection selection = endingSelection(); 02924 #ifdef DEBUG_COMMANDS 02925 kDebug() << "[indent selection]" << selection << endl; 02926 #endif 02927 NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement(); 02928 NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement(); 02929 02930 if (startBlock->id() == ID_LI && endBlock->id() == ID_LI && startBlock->parent() == endBlock->parent()) { 02931 #ifdef DEBUG_COMMANDS 02932 kDebug() << "[list items selected]" << endl; 02933 #endif 02934 bool firstItemSelected = !hasPreviousListItem(startBlock); 02935 bool lastItemSelected = !hasNextListItem(endBlock); 02936 bool listFullySelected = firstItemSelected && lastItemSelected; 02937 02938 #ifdef DEBUG_COMMANDS 02939 kDebug() << "[first/last item selected]" << firstItemSelected << lastItemSelected << endl; 02940 #endif 02941 02942 NodeImpl *listNode = startBlock->parent(); 02943 printEnclosingBlockTree(listNode); 02944 bool hasParentList = listNode->parent()->id() == ID_OL || listNode->parent()->id() == ID_UL; 02945 02946 if (!firstItemSelected && !lastItemSelected) { 02947 // split the list into 2 and reattach all the nodes before the first selected item to the second list 02948 RefPtr<NodeImpl> clonedList = listNode->cloneNode(false); 02949 NodeImpl *nextNode = 0; 02950 for (NodeImpl *node = listNode->firstChild(); node != startBlock; node = nextNode) { 02951 nextNode = node->nextSibling(); 02952 removeNode(node); 02953 appendNode(clonedList.get(), node); 02954 } 02955 insertNodeBefore(clonedList.get(), listNode); 02956 // so now the first item selected 02957 firstItemSelected = true; 02958 } 02959 02960 NodeImpl *nextNode = 0; 02961 for (NodeImpl *node = firstItemSelected ? startBlock : endBlock;; node = nextNode) { 02962 nextNode = firstItemSelected ? node->nextSibling() : node->previousSibling(); 02963 removeNode(node); 02964 if (firstItemSelected) 02965 insertNodeBefore(node, listNode); 02966 else 02967 insertNodeAfter(node, listNode); 02968 if (!hasParentList && node->id() == ID_LI) { 02969 insertNodeAfter(document()->createHTMLElement("BR"), node); 02970 removeNodePreservingChildren(node); 02971 } 02972 if (node == (firstItemSelected ? endBlock : startBlock)) 02973 break; 02974 } 02975 if (listFullySelected) 02976 removeNode(listNode); 02977 return; 02978 } 02979 02980 02981 if (startBlock == endBlock) { 02982 if (startBlock->id() == ID_BLOCKQUOTE) { 02983 removeNodePreservingChildren(startBlock); 02984 } else { 02985 #ifdef DEBUG_COMMANDS 02986 kDebug() << "[not the list or blockquote]" << endl; 02987 #endif 02988 } 02989 } else { 02990 #ifdef DEBUG_COMMANDS 02991 kDebug() << "[blocks not from one list are not supported yet]" << endl; 02992 #endif 02993 } 02994 } 02995 02996 void IndentOutdentCommandImpl::doApply() 02997 { 02998 if (m_commandType == Indent) 02999 indent(); 03000 else 03001 outdent(); 03002 } 03003 03004 //------------------------------------------------------------------------------------------ 03005 03006 } // namespace khtml 03007
KDE 4.6 API Reference