• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

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 "misc/htmlattrs.h"
00035 #include "misc/htmltags.h"
00036 #include "rendering/render_object.h"
00037 #include "rendering/render_style.h"
00038 #include "rendering/render_text.h"
00039 #include "xml/dom_docimpl.h"
00040 #include "xml/dom_elementimpl.h"
00041 #include "xml/dom_position.h"
00042 #include "xml/dom_positioniterator.h"
00043 #include "xml/dom_nodeimpl.h"
00044 #include "xml/dom_selection.h"
00045 #include "xml/dom_stringimpl.h"
00046 #include "xml/dom_textimpl.h"
00047 #include "xml/dom2_rangeimpl.h"
00048 #include "xml/dom2_viewsimpl.h"
00049 
00050 #include "khtml_part.h"
00051 #include "khtmlview.h"
00052 
00053 #include <QList>
00054 #include <limits.h>
00055 
00056 using DOM::AttrImpl;
00057 using DOM::CSSPrimitiveValue;
00058 using DOM::CSSPrimitiveValueImpl;
00059 using DOM::CSSProperty;
00060 using DOM::CSSStyleDeclarationImpl;
00061 using DOM::CSSValueImpl;
00062 using DOM::DocumentFragmentImpl;
00063 using DOM::DocumentImpl;
00064 using DOM::DOMString;
00065 using DOM::DOMStringImpl;
00066 using DOM::EditingTextImpl;
00067 using DOM::PositionIterator;
00068 using DOM::ElementImpl;
00069 using DOM::HTMLElementImpl;
00070 using DOM::HTMLImageElementImpl;
00071 using DOM::NamedAttrMapImpl;
00072 using DOM::Node;
00073 using DOM::NodeImpl;
00074 using DOM::NodeListImpl;
00075 using DOM::Position;
00076 using DOM::Range;
00077 using DOM::RangeImpl;
00078 using DOM::Selection;
00079 using DOM::TextImpl;
00080 using DOM::TreeWalkerImpl;
00081 
00082 #ifdef LOG_DISABLED
00083 #define debugPosition(a,b) ((void)0)
00084 #endif
00085 
00086 namespace khtml {
00087 
00088 
00089 static inline bool isNBSP(const QChar &c)
00090 {
00091     return c == QChar(0xa0);
00092 }
00093 
00094 static inline bool isWS(const QChar &c)
00095 {
00096     return c.isSpace() && c != QChar(0xa0);
00097 }
00098 
00099 static inline bool isWS(const DOMString &text)
00100 {
00101     if (text.length() != 1)
00102         return false;
00103 
00104     return isWS(text[0]);
00105 }
00106 
00107 static inline bool isWS(const Position &pos)
00108 {
00109     if (!pos.node())
00110         return false;
00111 
00112     if (!pos.node()->isTextNode())
00113         return false;
00114 
00115     const DOMString &string = static_cast<TextImpl *>(pos.node())->data();
00116     return isWS(string[pos.offset()]);
00117 }
00118 
00119 static bool shouldPruneNode(NodeImpl *node)
00120 {
00121     if (!node)
00122         return false;
00123 
00124     RenderObject *renderer = node->renderer();
00125     if (!renderer)
00126         return true;
00127 
00128     if (node->hasChildNodes())
00129         return false;
00130 
00131     if (node->rootEditableElement() == node)
00132         return false;
00133 
00134     if (renderer->isBR() || renderer->isReplaced())
00135         return false;
00136 
00137     if (node->isTextNode()) {
00138         TextImpl *text = static_cast<TextImpl *>(node);
00139         if (text->length() == 0)
00140             return true;
00141         return false;
00142     }
00143 
00144     if (!node->isHTMLElement()/* && !node->isXMLElementNode()*/)
00145         return false;
00146 
00147     if (node->id() == ID_BODY)
00148         return false;
00149 
00150     if (!node->isContentEditable())
00151         return false;
00152 
00153     return true;
00154 }
00155 
00156 static Position leadingWhitespacePosition(const Position &pos)
00157 {
00158     assert(pos.notEmpty());
00159 
00160     Selection selection(pos);
00161     Position prev = pos.previousCharacterPosition();
00162     if (prev != pos && prev.node()->inSameContainingBlockFlowElement(pos.node()) && prev.node()->isTextNode()) {
00163         DOMString string = static_cast<TextImpl *>(prev.node())->data();
00164         if (isWS(string[prev.offset()]))
00165             return prev;
00166     }
00167 
00168     return Position();
00169 }
00170 
00171 static Position trailingWhitespacePosition(const Position &pos)
00172 {
00173     assert(pos.notEmpty());
00174 
00175     if (pos.node()->isTextNode()) {
00176         TextImpl *textNode = static_cast<TextImpl *>(pos.node());
00177         if (pos.offset() >= (long)textNode->length()) {
00178             Position next = pos.nextCharacterPosition();
00179             if (next != pos && next.node()->inSameContainingBlockFlowElement(pos.node()) && next.node()->isTextNode()) {
00180                 DOMString string = static_cast<TextImpl *>(next.node())->data();
00181                 if (isWS(string[0]))
00182                     return next;
00183             }
00184         }
00185         else {
00186             DOMString string = static_cast<TextImpl *>(pos.node())->data();
00187             if (isWS(string[pos.offset()]))
00188                 return pos;
00189         }
00190     }
00191 
00192     return Position();
00193 }
00194 
00195 static bool textNodesAreJoinable(TextImpl *text1, TextImpl *text2)
00196 {
00197     assert(text1);
00198     assert(text2);
00199 
00200     return (text1->nextSibling() == text2);
00201 }
00202 
00203 static DOMString &nonBreakingSpaceString()
00204 {
00205     static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
00206     return nonBreakingSpaceString;
00207 }
00208 
00209 static DOMString &styleSpanClassString()
00210 {
00211     static DOMString styleSpanClassString = "khtml-style-span";
00212     return styleSpanClassString;
00213 }
00214 
00215 #ifndef LOG_DISABLED
00216 static void debugPosition(const char *prefix, const Position &pos)
00217 {
00218     kDebug(6200) << prefix << getTagName(pos.node()->id()) << pos.node() << pos.offset();
00219 }
00220 #endif
00221 
00222 //------------------------------------------------------------------------------------------
00223 // EditCommandImpl
00224 
00225 EditCommandImpl::EditCommandImpl(DocumentImpl *document)
00226     : SharedCommandImpl(), m_document(document), m_state(NotApplied), m_parent(0)
00227 {
00228     assert(m_document);
00229     assert(m_document->part());
00230     m_document->ref();
00231     m_startingSelection = m_document->part()->caret();
00232     m_endingSelection = m_startingSelection;
00233 }
00234 
00235 EditCommandImpl::~EditCommandImpl()
00236 {
00237     m_document->deref();
00238 }
00239 
00240 int EditCommandImpl::commandID() const
00241 {
00242     return EditCommandID;
00243 }
00244 
00245 void EditCommandImpl::apply()
00246 {
00247     assert(m_document);
00248     assert(m_document->part());
00249     assert(state() == NotApplied);
00250 
00251     doApply();
00252 
00253     m_state = Applied;
00254 
00255     if (!isCompositeStep()) {
00256         EditCommand cmd(this);
00257         m_document->part()->editor()->appliedEditing(cmd);
00258     }
00259 }
00260 
00261 void EditCommandImpl::unapply()
00262 {
00263     assert(m_document);
00264     assert(m_document->part());
00265     assert(state() == Applied);
00266 
00267     doUnapply();
00268 
00269     m_state = NotApplied;
00270 
00271     if (!isCompositeStep()) {
00272         EditCommand cmd(this);
00273         m_document->part()->editor()->unappliedEditing(cmd);
00274     }
00275 }
00276 
00277 void EditCommandImpl::reapply()
00278 {
00279     assert(m_document);
00280     assert(m_document->part());
00281     assert(state() == NotApplied);
00282 
00283     doReapply();
00284 
00285     m_state = Applied;
00286 
00287     if (!isCompositeStep()) {
00288         EditCommand cmd(this);
00289         m_document->part()->editor()->reappliedEditing(cmd);
00290     }
00291 }
00292 
00293 void EditCommandImpl::doReapply()
00294 {
00295     doApply();
00296 }
00297 
00298 void EditCommandImpl::setStartingSelection(const Selection &s)
00299 {
00300     m_startingSelection = s;
00301     EditCommand cmd( parent() );
00302     while (cmd.notNull()) {
00303         cmd.handle()->m_startingSelection = s;
00304         cmd = cmd.handle()->parent();
00305     }
00306 }
00307 
00308 void EditCommandImpl::setEndingSelection(const Selection &s)
00309 {
00310     m_endingSelection = s;
00311     EditCommand cmd = parent();
00312     while (cmd.notNull()) {
00313         cmd.handle()->m_endingSelection = s;
00314         cmd = cmd.handle()->parent();
00315     }
00316 }
00317 
00318 EditCommandImpl* EditCommandImpl::parent() const
00319 {
00320     return m_parent;
00321 }
00322 
00323 void EditCommandImpl::setParent(EditCommandImpl* cmd)
00324 {
00325     m_parent = cmd;
00326 }
00327 
00328 //------------------------------------------------------------------------------------------
00329 // CompositeEditCommandImpl
00330 
00331 CompositeEditCommandImpl::CompositeEditCommandImpl(DocumentImpl *document)
00332     : EditCommandImpl(document)
00333 {
00334 }
00335 
00336 CompositeEditCommandImpl::~CompositeEditCommandImpl()
00337 {
00338 }
00339 
00340 int CompositeEditCommandImpl::commandID() const
00341 {
00342     return CompositeEditCommandID;
00343 }
00344 
00345 void CompositeEditCommandImpl::doUnapply()
00346 {
00347     if (m_cmds.count() == 0) {
00348         return;
00349     }
00350 
00351     for (int i = m_cmds.count() - 1; i >= 0; --i)
00352         m_cmds[i].unapply();
00353 
00354     setState(NotApplied);
00355 }
00356 
00357 void CompositeEditCommandImpl::doReapply()
00358 {
00359     if (m_cmds.count() == 0) {
00360         return;
00361     }
00362     QMutableListIterator<EditCommand> it(m_cmds);
00363     while (it.hasNext())
00364         it.next().reapply();
00365 
00366     setState(Applied);
00367 }
00368 
00369 //
00370 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
00371 //
00372 void CompositeEditCommandImpl::applyCommandToComposite(EditCommand &cmd)
00373 {
00374     cmd.setStartingSelection(endingSelection());//###?
00375     cmd.setEndingSelection(endingSelection());
00376     cmd.handle()->setParent(this);
00377     cmd.apply();
00378     m_cmds.append(cmd);
00379 }
00380 
00381 void CompositeEditCommandImpl::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
00382 {
00383     InsertNodeBeforeCommand cmd(document(), insertChild, refChild);
00384     applyCommandToComposite(cmd);
00385 }
00386 
00387 void CompositeEditCommandImpl::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
00388 {
00389     if (refChild->parentNode()->lastChild() == refChild) {
00390         appendNode(refChild->parentNode(), insertChild);
00391     }
00392     else {
00393         assert(refChild->nextSibling());
00394         insertNodeBefore(insertChild, refChild->nextSibling());
00395     }
00396 }
00397 
00398 void CompositeEditCommandImpl::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
00399 {
00400     if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
00401         NodeImpl *child = refChild->firstChild();
00402         for (long i = 0; child && i < offset; i++)
00403             child = child->nextSibling();
00404         if (child)
00405             insertNodeBefore(insertChild, child);
00406         else
00407             appendNode(refChild, insertChild);
00408     }
00409     else if (refChild->caretMinOffset() >= offset) {
00410         insertNodeBefore(insertChild, refChild);
00411     }
00412     else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
00413         splitTextNode(static_cast<TextImpl *>(refChild), offset);
00414         insertNodeBefore(insertChild, refChild);
00415     }
00416     else {
00417         insertNodeAfter(insertChild, refChild);
00418     }
00419 }
00420 
00421 void CompositeEditCommandImpl::appendNode(NodeImpl *parent, NodeImpl *appendChild)
00422 {
00423     AppendNodeCommand cmd(document(), parent, appendChild);
00424     applyCommandToComposite(cmd);
00425 }
00426 
00427 void CompositeEditCommandImpl::removeNode(NodeImpl *removeChild)
00428 {
00429     RemoveNodeCommand cmd(document(), removeChild);
00430     applyCommandToComposite(cmd);
00431 }
00432 
00433 void CompositeEditCommandImpl::removeNodeAndPrune(NodeImpl *pruneNode, NodeImpl *stopNode)
00434 {
00435     RemoveNodeAndPruneCommand cmd(document(), pruneNode, stopNode);
00436     applyCommandToComposite(cmd);
00437 }
00438 
00439 void CompositeEditCommandImpl::removeNodePreservingChildren(NodeImpl *removeChild)
00440 {
00441     RemoveNodePreservingChildrenCommand cmd(document(), removeChild);
00442     applyCommandToComposite(cmd);
00443 }
00444 
00445 void CompositeEditCommandImpl::splitTextNode(TextImpl *text, long offset)
00446 {
00447     SplitTextNodeCommand cmd(document(), text, offset);
00448     applyCommandToComposite(cmd);
00449 }
00450 
00451 void CompositeEditCommandImpl::joinTextNodes(TextImpl *text1, TextImpl *text2)
00452 {
00453     JoinTextNodesCommand cmd(document(), text1, text2);
00454     applyCommandToComposite(cmd);
00455 }
00456 
00457 void CompositeEditCommandImpl::inputText(const DOMString &text)
00458 {
00459     InputTextCommand cmd(document());
00460     applyCommandToComposite(cmd);
00461     cmd.input(text);
00462 }
00463 
00464 void CompositeEditCommandImpl::insertText(TextImpl *node, long offset, const DOMString &text)
00465 {
00466     InsertTextCommand cmd(document(), node, offset, text);
00467     applyCommandToComposite(cmd);
00468 }
00469 
00470 void CompositeEditCommandImpl::deleteText(TextImpl *node, long offset, long count)
00471 {
00472     DeleteTextCommand cmd(document(), node, offset, count);
00473     applyCommandToComposite(cmd);
00474 }
00475 
00476 void CompositeEditCommandImpl::replaceText(TextImpl *node, long offset, long count, const DOMString &replacementText)
00477 {
00478     DeleteTextCommand deleteCommand(document(), node, offset, count);
00479     applyCommandToComposite(deleteCommand);
00480     InsertTextCommand insertCommand(document(), node, offset, replacementText);
00481     applyCommandToComposite(insertCommand);
00482 }
00483 
00484 void CompositeEditCommandImpl::deleteSelection()
00485 {
00486     if (endingSelection().state() == Selection::RANGE) {
00487         DeleteSelectionCommand cmd(document());
00488         applyCommandToComposite(cmd);
00489     }
00490 }
00491 
00492 void CompositeEditCommandImpl::deleteSelection(const Selection &selection)
00493 {
00494     if (selection.state() == Selection::RANGE) {
00495         DeleteSelectionCommand cmd(document(), selection);
00496         applyCommandToComposite(cmd);
00497     }
00498 }
00499 
00500 void CompositeEditCommandImpl::deleteCollapsibleWhitespace()
00501 {
00502     DeleteCollapsibleWhitespaceCommand cmd(document());
00503     applyCommandToComposite(cmd);
00504 }
00505 
00506 void CompositeEditCommandImpl::deleteCollapsibleWhitespace(const Selection &selection)
00507 {
00508     DeleteCollapsibleWhitespaceCommand cmd(document(), selection);
00509     applyCommandToComposite(cmd);
00510 }
00511 
00512 void CompositeEditCommandImpl::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
00513 {
00514     RemoveCSSPropertyCommand cmd(document(), decl, property);
00515     applyCommandToComposite(cmd);
00516 }
00517 
00518 void CompositeEditCommandImpl::removeNodeAttribute(ElementImpl *element, int attribute)
00519 {
00520     RemoveNodeAttributeCommand cmd(document(), element, attribute);
00521     applyCommandToComposite(cmd);
00522 }
00523 
00524 void CompositeEditCommandImpl::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
00525 {
00526     SetNodeAttributeCommand cmd(document(), element, attribute, value);
00527     applyCommandToComposite(cmd);
00528 }
00529 
00530 ElementImpl *CompositeEditCommandImpl::createTypingStyleElement() const
00531 {
00532     int exceptionCode = 0;
00533     ElementImpl *styleElement = document()->createHTMLElement("SPAN");
00534 //     assert(exceptionCode == 0);
00535 
00536     styleElement->setAttribute(ATTR_STYLE, document()->part()->editor()->typingStyle()->cssText().implementation());
00537 //     assert(exceptionCode == 0);
00538 
00539     styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
00540     assert(exceptionCode == 0);
00541 
00542     return styleElement;
00543 }
00544 
00545 //==========================================================================================
00546 // Concrete commands
00547 //------------------------------------------------------------------------------------------
00548 // AppendNodeCommandImpl
00549 
00550 AppendNodeCommandImpl::AppendNodeCommandImpl(DocumentImpl *document, NodeImpl *parentNode, NodeImpl *appendChild)
00551     : EditCommandImpl(document), m_parentNode(parentNode), m_appendChild(appendChild)
00552 {
00553     assert(m_parentNode);
00554     m_parentNode->ref();
00555 
00556     assert(m_appendChild);
00557     m_appendChild->ref();
00558 }
00559 
00560 AppendNodeCommandImpl::~AppendNodeCommandImpl()
00561 {
00562     if (m_parentNode)
00563         m_parentNode->deref();
00564     if (m_appendChild)
00565         m_appendChild->deref();
00566 }
00567 
00568 int AppendNodeCommandImpl::commandID() const
00569 {
00570     return AppendNodeCommandID;
00571 }
00572 
00573 void AppendNodeCommandImpl::doApply()
00574 {
00575     assert(m_parentNode);
00576     assert(m_appendChild);
00577 
00578     int exceptionCode = 0;
00579     m_parentNode->appendChild(m_appendChild, exceptionCode);
00580     assert(exceptionCode == 0);
00581 }
00582 
00583 void AppendNodeCommandImpl::doUnapply()
00584 {
00585     assert(m_parentNode);
00586     assert(m_appendChild);
00587     assert(state() == Applied);
00588 
00589     int exceptionCode = 0;
00590     m_parentNode->removeChild(m_appendChild, exceptionCode);
00591     assert(exceptionCode == 0);
00592 }
00593 
00594 //------------------------------------------------------------------------------------------
00595 // ApplyStyleCommandImpl
00596 
00597 ApplyStyleCommandImpl::ApplyStyleCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *style)
00598     : CompositeEditCommandImpl(document), m_style(style)
00599 {
00600     assert(m_style);
00601     m_style->ref();
00602 }
00603 
00604 ApplyStyleCommandImpl::~ApplyStyleCommandImpl()
00605 {
00606     assert(m_style);
00607     m_style->deref();
00608 }
00609 
00610 int ApplyStyleCommandImpl::commandID() const
00611 {
00612     return ApplyStyleCommandID;
00613 }
00614 
00615 void ApplyStyleCommandImpl::doApply()
00616 {
00617     if (endingSelection().state() != Selection::RANGE)
00618         return;
00619 
00620     // adjust to the positions we want to use for applying style
00621     Position start(endingSelection().start().equivalentDownstreamPosition().equivalentRangeCompliantPosition());
00622     Position end(endingSelection().end().equivalentUpstreamPosition());
00623 
00624     // remove style from the selection
00625     removeStyle(start, end);
00626     bool splitStart = splitTextAtStartIfNeeded(start, end);
00627     if (splitStart) {
00628         start = endingSelection().start();
00629         end = endingSelection().end();
00630     }
00631     splitTextAtEndIfNeeded(start, end);
00632     start = endingSelection().start();
00633     end = endingSelection().end();
00634 
00635 
00636     if (start.node() == end.node()) {
00637         // simple case...start and end are the same node
00638         applyStyleIfNeeded(start.node(), end.node());
00639     }
00640     else {
00641         NodeImpl *node = start.node();
00642         while (1) {
00643             if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
00644                 NodeImpl *runStart = node;
00645                 while (1) {
00646                     if (runStart->parentNode() != node->parentNode() || node->isHTMLElement() || node == end.node() ||
00647                         (node->renderer() && !node->renderer()->isInline())) {
00648                         applyStyleIfNeeded(runStart, node);
00649                         break;
00650                     }
00651                     node = node->traverseNextNode();
00652                 }
00653             }
00654             if (node == end.node())
00655                 break;
00656             node = node->traverseNextNode();
00657         }
00658     }
00659 }
00660 
00661 //------------------------------------------------------------------------------------------
00662 // ApplyStyleCommandImpl: style-removal helpers
00663 
00664 bool ApplyStyleCommandImpl::isHTMLStyleNode(HTMLElementImpl *elem)
00665 {
00666     QListIterator<CSSProperty*> it(*(style()->values()));
00667     while (it.hasNext()) {
00668         CSSProperty *property = it.next();
00669         switch (property->id()) {
00670             case CSS_PROP_FONT_WEIGHT:
00671                 if (elem->id() == ID_B)
00672                     return true;
00673                 break;
00674             case CSS_PROP_FONT_STYLE:
00675                 if (elem->id() == ID_I)
00676                     return true;
00677                 break;
00678         }
00679     }
00680 
00681     return false;
00682 }
00683 
00684 void ApplyStyleCommandImpl::removeHTMLStyleNode(HTMLElementImpl *elem)
00685 {
00686     // This node can be removed.
00687     // EDIT FIXME: This does not handle the case where the node
00688     // has attributes. But how often do people add attributes to <B> tags?
00689     // Not so often I think.
00690     assert(elem);
00691     removeNodePreservingChildren(elem);
00692 }
00693 
00694 void ApplyStyleCommandImpl::removeCSSStyle(HTMLElementImpl *elem)
00695 {
00696     assert(elem);
00697 
00698     CSSStyleDeclarationImpl *decl = elem->inlineStyleDecls();
00699     if (!decl)
00700         return;
00701 
00702     QListIterator<CSSProperty*> it(*(style()->values()));
00703     while ( it.hasNext() ) {
00704         CSSProperty *property = it.next();
00705         if (decl->getPropertyCSSValue(property->id()))
00706             removeCSSProperty(decl, property->id());
00707     }
00708 
00709     if (elem->id() == ID_SPAN) {
00710         // Check to see if the span is one we added to apply style.
00711         // If it is, and there are no more attributes on the span other than our
00712         // class marker, remove the span.
00713         NamedAttrMapImpl *map = elem->attributes();
00714         if (map && map->length() == 1 && elem->getAttribute(ATTR_CLASS) == styleSpanClassString())
00715             removeNodePreservingChildren(elem);
00716     }
00717 }
00718 
00719 void ApplyStyleCommandImpl::removeStyle(const Position &start, const Position &end)
00720 {
00721     NodeImpl *node = start.node();
00722     while (1) {
00723         NodeImpl *next = node->traverseNextNode();
00724         if (node->isHTMLElement() && nodeFullySelected(node)) {
00725             HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
00726             if (isHTMLStyleNode(elem))
00727                 removeHTMLStyleNode(elem);
00728             else
00729                 removeCSSStyle(elem);
00730         }
00731         if (node == end.node())
00732             break;
00733         node = next;
00734     }
00735 }
00736 
00737 bool ApplyStyleCommandImpl::nodeFullySelected(const NodeImpl *node) const
00738 {
00739     assert(node);
00740 
00741     Position end(endingSelection().end().equivalentUpstreamPosition());
00742 
00743     if (node == end.node())
00744         return end.offset() >= node->caretMaxOffset();
00745 
00746     for (NodeImpl *child = node->lastChild(); child; child = child->lastChild()) {
00747         if (child == end.node())
00748             return end.offset() >= child->caretMaxOffset();
00749     }
00750 
00751     return node == end.node() || !node->isAncestor(end.node());
00752 }
00753 
00754 //------------------------------------------------------------------------------------------
00755 // ApplyStyleCommandImpl: style-application helpers
00756 
00757 
00758 bool ApplyStyleCommandImpl::splitTextAtStartIfNeeded(const Position &start, const Position &end)
00759 {
00760     if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
00761         long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
00762         TextImpl *text = static_cast<TextImpl *>(start.node());
00763         SplitTextNodeCommand cmd(document(), text, start.offset());
00764         applyCommandToComposite(cmd);
00765         setEndingSelection(Selection(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment)));
00766         return true;
00767     }
00768     return false;
00769 }
00770 
00771 NodeImpl *ApplyStyleCommandImpl::splitTextAtEndIfNeeded(const Position &start, const Position &end)
00772 {
00773     if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
00774         TextImpl *text = static_cast<TextImpl *>(end.node());
00775         SplitTextNodeCommand cmd(document(), text, end.offset());
00776         applyCommandToComposite(cmd);
00777         NodeImpl *startNode = start.node() == end.node() ? cmd.node()->previousSibling() : start.node();
00778         assert(startNode);
00779         setEndingSelection(Selection(Position(startNode, start.offset()), Position(cmd.node()->previousSibling(), cmd.node()->previousSibling()->caretMaxOffset())));
00780         return cmd.node()->previousSibling();
00781     }
00782     return end.node();
00783 }
00784 
00785 void ApplyStyleCommandImpl::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
00786 {
00787     assert(startNode);
00788     assert(endNode);
00789     assert(element);
00790 
00791     NodeImpl *node = startNode;
00792     while (1) {
00793         NodeImpl *next = node->traverseNextNode();
00794         if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
00795             removeNode(node);
00796             appendNode(element, node);
00797         }
00798         if (node == endNode)
00799             break;
00800         node = next;
00801     }
00802 }
00803 
00804 void ApplyStyleCommandImpl::applyStyleIfNeeded(DOM::NodeImpl *startNode, DOM::NodeImpl *endNode)
00805 {
00806     StyleChange styleChange = computeStyleChange(Position(startNode, 0), style());
00807     int exceptionCode = 0;
00808 
00809     if (styleChange.cssStyle.length() > 0) {
00810         ElementImpl *styleElement = document()->createHTMLElement("SPAN");
00811         assert(exceptionCode == 0);
00812         styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle);
00813         styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
00814         insertNodeBefore(styleElement, startNode);
00815         surroundNodeRangeWithElement(startNode, endNode, styleElement);
00816     }
00817 
00818     if (styleChange.applyBold) {
00819         ElementImpl *boldElement = document()->createHTMLElement("B");
00820         assert(exceptionCode == 0);
00821         insertNodeBefore(boldElement, startNode);
00822         surroundNodeRangeWithElement(startNode, endNode, boldElement);
00823     }
00824 
00825     if (styleChange.applyItalic) {
00826         ElementImpl *italicElement = document()->createHTMLElement("I");
00827         assert(exceptionCode == 0);
00828         insertNodeBefore(italicElement, startNode);
00829         surroundNodeRangeWithElement(startNode, endNode, italicElement);
00830     }
00831 }
00832 
00833 bool ApplyStyleCommandImpl::currentlyHasStyle(const Position &pos, const CSSProperty *property) const
00834 {
00835     assert(pos.notEmpty());
00836     CSSStyleDeclarationImpl *decl = document()->defaultView()->getComputedStyle(pos.element(), 0);
00837     assert(decl);
00838     CSSValueImpl *value = decl->getPropertyCSSValue(property->id());
00839     return strcasecmp(value->cssText(), property->value()->cssText()) == 0;
00840 }
00841 
00842 ApplyStyleCommandImpl::StyleChange ApplyStyleCommandImpl::computeStyleChange(const Position &insertionPoint, CSSStyleDeclarationImpl *style)
00843 {
00844     assert(insertionPoint.notEmpty());
00845     assert(style);
00846 
00847     StyleChange styleChange;
00848 
00849     QListIterator<CSSProperty*> it(*(style->values()));
00850     while ( it.hasNext() ) {
00851         CSSProperty *property = it.next();
00852         if (!currentlyHasStyle(insertionPoint, property)) {
00853             switch (property->id()) {
00854                 case CSS_PROP_FONT_WEIGHT:
00855                     if (strcasecmp(property->value()->cssText(), "bold") == 0)
00856                         styleChange.applyBold = true;
00857                     else
00858                         styleChange.cssStyle += property->cssText();
00859                     break;
00860                 case CSS_PROP_FONT_STYLE: {
00861                         DOMString cssText(property->value()->cssText());
00862                         if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0)
00863                             styleChange.applyItalic = true;
00864                         else
00865                             styleChange.cssStyle += property->cssText();
00866                     }
00867                     break;
00868                 default:
00869                     styleChange.cssStyle += property->cssText();
00870                     break;
00871             }
00872         }
00873     }
00874     return styleChange;
00875 }
00876 
00877 Position ApplyStyleCommandImpl::positionInsertionPoint(Position pos)
00878 {
00879     if (pos.node()->isTextNode() && (pos.offset() > 0 && pos.offset() < pos.node()->maxOffset())) {
00880         SplitTextNodeCommand split(document(), static_cast<TextImpl *>(pos.node()), pos.offset());
00881         split.apply();
00882         pos = Position(split.node(), 0);
00883     }
00884 
00885 #if 0
00886     // EDIT FIXME: If modified to work with the internals of applying style,
00887     // this code can work to optimize cases where a style change is taking place on
00888     // a boundary between nodes where one of the nodes has the desired style. In other
00889     // words, it is possible for content to be merged into existing nodes rather than adding
00890     // additional markup.
00891     if (currentlyHasStyle(pos))
00892         return pos;
00893 
00894     // try next node
00895     if (pos.offset() >= pos.node()->caretMaxOffset()) {
00896         NodeImpl *nextNode = pos.node()->traverseNextNode();
00897         if (nextNode) {
00898             Position next = Position(nextNode, 0);
00899             if (currentlyHasStyle(next))
00900                 return next;
00901         }
00902     }
00903 
00904     // try previous node
00905     if (pos.offset() <= pos.node()->caretMinOffset()) {
00906         NodeImpl *prevNode = pos.node()->traversePreviousNode();
00907         if (prevNode) {
00908             Position prev = Position(prevNode, prevNode->maxOffset());
00909             if (currentlyHasStyle(prev))
00910                 return prev;
00911         }
00912     }
00913 #endif
00914 
00915     return pos;
00916 }
00917 
00918 //------------------------------------------------------------------------------------------
00919 // DeleteCollapsibleWhitespaceCommandImpl
00920 
00921 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document)
00922     : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_hasSelectionToCollapse(false)
00923 {
00924 }
00925 
00926 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document, const Selection &selection)
00927     : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_selectionToCollapse(selection), m_hasSelectionToCollapse(true)
00928 {
00929 }
00930 
00931 DeleteCollapsibleWhitespaceCommandImpl::~DeleteCollapsibleWhitespaceCommandImpl()
00932 {
00933 }
00934 
00935 int DeleteCollapsibleWhitespaceCommandImpl::commandID() const
00936 {
00937     return DeleteCollapsibleWhitespaceCommandID;
00938 }
00939 
00940 static bool shouldDeleteUpstreamPosition(const Position &pos)
00941 {
00942     if (!pos.node()->isTextNode())
00943         return false;
00944 
00945     RenderObject *renderer = pos.node()->renderer();
00946     if (!renderer)
00947         return true;
00948 
00949     TextImpl *textNode = static_cast<TextImpl *>(pos.node());
00950     if (pos.offset() >= (long)textNode->length())
00951         return false;
00952 
00953     if (pos.isLastRenderedPositionInEditableBlock())
00954         return false;
00955 
00956     if (pos.isFirstRenderedPositionOnLine() || pos.isLastRenderedPositionOnLine())
00957         return false;
00958 
00959     RenderText *textRenderer = static_cast<RenderText *>(renderer);
00960     for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
00961         if (pos.offset() < box->m_start) {
00962             return true;
00963         }
00964         if (pos.offset() >= box->m_start && pos.offset() < box->m_start + box->m_len)
00965             return false;
00966     }
00967 
00968     return true;
00969 }
00970 
00971 Position DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace(const Position &pos)
00972 {
00973     Position upstream = pos.equivalentUpstreamPosition();
00974     Position downstream = pos.equivalentDownstreamPosition();
00975 
00976     bool del = shouldDeleteUpstreamPosition(upstream);
00977 
00978     kDebug(6200) << "pos:" << getTagName(pos.node()->id()) << "["<< pos.node() << ":" << pos.offset() << "]";
00979     if (upstream == downstream) {
00980         kDebug(6200) << "same:" << getTagName(upstream.node()->id()) << "["<< upstream.node()<< ":" << upstream.offset()<< "]";
00981     }
00982     else {
00983         kDebug(6200) << "upstream:" << ( del ? "DELETE" : "SKIP") << getTagName(upstream.node()->id())<< "["<< upstream.node() << ":" <<  upstream.offset()<< "]";
00984         PositionIterator it(upstream);
00985         for (it.next(); it.current() != downstream; it.next()) {
00986             if (it.current().node()->isTextNode() && (long)static_cast<TextImpl *>(it.current().node())->length() == it.current().offset())
00987                 kDebug(6200) << "   node:    AT END"<< getTagName(it.current().node()->id())<< "["<< it.current().node()<< ":" << it.current().offset()<< "]";
00988             else
00989                 kDebug(6200) << "   node:    DELETE"<< getTagName(it.current().node()->id())<< "["<< it.current().node()<< ":" << it.current().offset()<< "]";
00990         }
00991         kDebug(6200) << "downstream:" << getTagName(downstream.node()->id()) << "["<<  downstream.node() << ":" << downstream.offset()<< "]";
00992     }
00993 
00994     if (upstream == downstream)
00995         return upstream;
00996 
00997     PositionIterator it(upstream);
00998     Position deleteStart = upstream;
00999     if (!del) {
01000         deleteStart = it.peekNext();
01001         if (deleteStart == downstream)
01002             return upstream;
01003     }
01004 
01005     Position endingPosition = upstream;
01006 
01007     while (it.current() != downstream) {
01008 
01009         Position next = it.peekNext();
01010         if (next.node() != deleteStart.node()) {
01011             assert(deleteStart.node()->isTextNode());
01012             TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node());
01013             unsigned long count = it.current().offset() - deleteStart.offset();
01014             if (count == textNode->length()) {
01015                 kDebug(6200) << "   removeNodeAndPrune 1:" << textNode;
01016                 if (textNode == endingPosition.node())
01017                     endingPosition = Position(next.node(), next.node()->caretMinOffset());
01018                 removeNodeAndPrune(textNode);
01019             }
01020             else {
01021                 kDebug(6200) << "   deleteText 1:" <<  textNode << "t len:" << textNode->length()<<"start:" <<  deleteStart.offset() << "del len:" << (it.current().offset() - deleteStart.offset());
01022                 deleteText(textNode, deleteStart.offset(), count);
01023             }
01024             deleteStart = next;
01025         }
01026         else if (next == downstream) {
01027             assert(deleteStart.node() == downstream.node());
01028             assert(downstream.node()->isTextNode());
01029             TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node());
01030             unsigned long count = downstream.offset() - deleteStart.offset();
01031             assert(count <= textNode->length());
01032             if (count == textNode->length()) {
01033                 kDebug(6200) << "   removeNodeAndPrune 2:"<<textNode;
01034                 removeNodeAndPrune(textNode);
01035             }
01036             else {
01037                 kDebug(6200) << "   deleteText 2:"<< textNode<< "t len:" <<  textNode->length() <<"start:" <<deleteStart.offset() << "del len:" <<  count;
01038                 deleteText(textNode, deleteStart.offset(), count);
01039                 m_charactersDeleted = count;
01040                 endingPosition = Position(downstream.node(), downstream.offset() - m_charactersDeleted);
01041             }
01042         }
01043 
01044         it.setPosition(next);
01045     }
01046 
01047     return endingPosition;
01048 }
01049 
01050 void DeleteCollapsibleWhitespaceCommandImpl::doApply()
01051 {
01052     // If selection has not been set to a custom selection when the command was created,
01053     // use the current ending selection.
01054     if (!m_hasSelectionToCollapse)
01055         m_selectionToCollapse = endingSelection();
01056     int state = m_selectionToCollapse.state();
01057     if (state == Selection::CARET) {
01058         Position endPosition = deleteWhitespace(m_selectionToCollapse.start());
01059         setEndingSelection(endPosition);
01060         kDebug(6200) << "-----------------------------------------------------";
01061     }
01062     else if (state == Selection::RANGE) {
01063         Position startPosition = deleteWhitespace(m_selectionToCollapse.start());
01064         kDebug(6200) <<  "-----------------------------------------------------";
01065         Position endPosition = m_selectionToCollapse.end();
01066         if (m_charactersDeleted > 0 && startPosition.node() == endPosition.node()) {
01067             kDebug(6200) << "adjust end position by" << m_charactersDeleted;
01068             endPosition = Position(endPosition.node(), endPosition.offset() - m_charactersDeleted);
01069         }
01070         endPosition = deleteWhitespace(endPosition);
01071         setEndingSelection(Selection(startPosition, endPosition));
01072         kDebug(6200) << "=====================================================";
01073     }
01074 }
01075 
01076 //------------------------------------------------------------------------------------------
01077 // DeleteSelectionCommandImpl
01078 
01079 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document)
01080     : CompositeEditCommandImpl(document), m_hasSelectionToDelete(false)
01081 {
01082 }
01083 
01084 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document, const Selection &selection)
01085     : CompositeEditCommandImpl(document), m_selectionToDelete(selection), m_hasSelectionToDelete(true)
01086 {
01087 }
01088 
01089 DeleteSelectionCommandImpl::~DeleteSelectionCommandImpl()
01090 {
01091 }
01092 
01093 int DeleteSelectionCommandImpl::commandID() const
01094 {
01095     return DeleteSelectionCommandID;
01096 }
01097 
01098 void DeleteSelectionCommandImpl::joinTextNodesWithSameStyle()
01099 {
01100     Selection selection = endingSelection();
01101 
01102     if (selection.state() != Selection::CARET)
01103         return;
01104 
01105     Position pos(selection.start());
01106 
01107     if (!pos.node()->isTextNode())
01108         return;
01109 
01110     TextImpl *textNode = static_cast<TextImpl *>(pos.node());
01111 
01112     if (pos.offset() == 0) {
01113         PositionIterator it(pos);
01114         Position prev = it.previous();
01115         if (prev == pos)
01116             return;
01117         if (prev.node()->isTextNode()) {
01118             TextImpl *prevTextNode = static_cast<TextImpl *>(prev.node());
01119             if (textNodesAreJoinable(prevTextNode, textNode)) {
01120                 joinTextNodes(prevTextNode, textNode);
01121                 setEndingSelection(Position(textNode, prevTextNode->length()));
01122                 kDebug(6200) << "joinTextNodesWithSameStyle [1]";
01123             }
01124         }
01125     }
01126     else if (pos.offset() == (long)textNode->length()) {
01127         PositionIterator it(pos);
01128         Position next = it.next();
01129         if (next == pos)
01130             return;
01131         if (next.node()->isTextNode()) {
01132             TextImpl *nextTextNode = static_cast<TextImpl *>(next.node());
01133             if (textNodesAreJoinable(textNode, nextTextNode)) {
01134                 joinTextNodes(textNode, nextTextNode);
01135                 setEndingSelection(Position(nextTextNode, pos.offset()));
01136                 kDebug(6200) << "joinTextNodesWithSameStyle [2]";
01137             }
01138         }
01139     }
01140 }
01141 
01142 bool DeleteSelectionCommandImpl::containsOnlyWhitespace(const Position &start, const Position &end)
01143 {
01144     // Returns whether the range contains only whitespace characters.
01145     // This is inclusive of the start, but not of the end.
01146     PositionIterator it(start);
01147     while (!it.atEnd()) {
01148         if (!it.current().node()->isTextNode())
01149             return false;
01150         const DOMString &text = static_cast<TextImpl *>(it.current().node())->data();
01151         // EDIT FIXME: signed/unsigned mismatch
01152         if (text.length() > INT_MAX)
01153             return false;
01154         if (it.current().offset() < (int)text.length() && !isWS(text[it.current().offset()]))
01155             return false;
01156         it.next();
01157         if (it.current() == end)
01158             break;
01159     }
01160     return true;
01161 }
01162 
01163 void DeleteSelectionCommandImpl::doApply()
01164 {
01165     // If selection has not been set to a custom selection when the command was created,
01166     // use the current ending selection.
01167     if (!m_hasSelectionToDelete)
01168         m_selectionToDelete = endingSelection();
01169 
01170     if (m_selectionToDelete.state() != Selection::RANGE)
01171         return;
01172 
01173     deleteCollapsibleWhitespace(m_selectionToDelete);
01174     Selection selection = endingSelection();
01175 
01176     Position upstreamStart(selection.start().equivalentUpstreamPosition());
01177     Position downstreamStart(selection.start().equivalentDownstreamPosition());
01178     Position upstreamEnd(selection.end().equivalentUpstreamPosition());
01179     Position downstreamEnd(selection.end().equivalentDownstreamPosition());
01180 
01181     if (upstreamStart == downstreamEnd)
01182         // after collapsing whitespace, selection is empty...no work to do
01183         return;
01184 
01185     Position endingPosition;
01186     bool adjustEndingPositionDownstream = false;
01187 
01188     bool onlyWhitespace = containsOnlyWhitespace(upstreamStart, downstreamEnd);
01189 
01190     bool startCompletelySelected = !onlyWhitespace &&
01191         (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset() &&
01192         ((downstreamStart.node() != upstreamEnd.node()) ||
01193          (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset())));
01194 
01195     bool endCompletelySelected = !onlyWhitespace &&
01196         (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset() &&
01197         ((downstreamStart.node() != upstreamEnd.node()) ||
01198          (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset())));
01199 
01200     unsigned long startRenderedOffset = downstreamStart.renderedOffset();
01201 
01202     bool startAtStartOfRootEditableElement = startRenderedOffset == 0 && downstreamStart.inFirstEditableInRootEditableElement();
01203     bool startAtStartOfBlock = startAtStartOfRootEditableElement ||
01204         (startRenderedOffset == 0 && downstreamStart.inFirstEditableInContainingEditableBlock());
01205     bool endAtEndOfBlock = downstreamEnd.isLastRenderedPositionInEditableBlock();
01206 
01207     NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement();
01208     NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement();
01209     bool startBlockEndBlockAreSiblings = startBlock->parentNode() == endBlock->parentNode();
01210 
01211     debugPosition("upstreamStart:       ", upstreamStart);
01212     debugPosition("downstreamStart:     ", downstreamStart);
01213     debugPosition("upstreamEnd:         ", upstreamEnd);
01214     debugPosition("downstreamEnd:       ", downstreamEnd);
01215     kDebug(6200) << "start selected:" << (startCompletelySelected ? "YES" : "NO");
01216     kDebug(6200) << "at start block:" << (startAtStartOfBlock ? "YES" : "NO");
01217     kDebug(6200) << "at start root block:"<< (startAtStartOfRootEditableElement ? "YES" : "NO");
01218     kDebug(6200) << "at end block:"<< (endAtEndOfBlock ? "YES" : "NO");
01219     kDebug(6200) << "only whitespace:"<< (onlyWhitespace ? "YES" : "NO");
01220 
01221     // Determine where to put the caret after the deletion
01222     if (startAtStartOfBlock) {
01223         kDebug(6200) << "ending position case 1";
01224         endingPosition = Position(startBlock, 0);
01225         adjustEndingPositionDownstream = true;
01226     }
01227     else if (!startCompletelySelected) {
01228         kDebug(6200) << "ending position case 2";
01229         endingPosition = upstreamStart;
01230         if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1)
01231             adjustEndingPositionDownstream = true;
01232     }
01233     else if (upstreamStart != downstreamStart) {
01234         kDebug(6200) << "ending position case 3";
01235         endingPosition = upstreamStart;
01236         if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1)
01237             adjustEndingPositionDownstream = true;
01238     }
01239 
01240     //
01241     // Figure out the whitespace conversions to do
01242     //
01243     if ((startAtStartOfBlock && !endAtEndOfBlock) || (!startCompletelySelected && adjustEndingPositionDownstream)) {
01244         // convert trailing whitespace
01245         Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
01246         if (trailing.notEmpty()) {
01247             debugPosition("convertTrailingWhitespace: ", trailing);
01248             Position collapse = trailing.nextCharacterPosition();
01249             if (collapse != trailing)
01250                 deleteCollapsibleWhitespace(collapse);
01251             TextImpl *textNode = static_cast<TextImpl *>(trailing.node());
01252             replaceText(textNode, trailing.offset(), 1, nonBreakingSpaceString());
01253         }
01254     }
01255     else if (!startAtStartOfBlock && endAtEndOfBlock) {
01256         // convert leading whitespace
01257         Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
01258         if (leading.notEmpty()) {
01259             debugPosition("convertLeadingWhitespace:  ", leading);
01260             TextImpl *textNode = static_cast<TextImpl *>(leading.node());
01261             replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
01262         }
01263     }
01264     else if (!startAtStartOfBlock && !endAtEndOfBlock) {
01265         // convert contiguous whitespace
01266         Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
01267         Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
01268         if (leading.notEmpty() && trailing.notEmpty()) {
01269             debugPosition("convertLeadingWhitespace [contiguous]:  ", leading);
01270             TextImpl *textNode = static_cast<TextImpl *>(leading.node());
01271             replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
01272         }
01273     }
01274 
01275     //
01276     // Do the delete
01277     //
01278     NodeImpl *n = downstreamStart.node()->traverseNextNode();
01279 
01280     // work on start node
01281     if (startCompletelySelected) {
01282         kDebug(6200) << "start node delete case 1";
01283         removeNodeAndPrune(downstreamStart.node(), startBlock);
01284     }
01285     else if (onlyWhitespace) {
01286         // Selection only contains whitespace. This is really a special-case to
01287         // handle significant whitespace that is collapsed at the end of a line,
01288         // but also handles deleting a space in mid-line.
01289         kDebug(6200) << "start node delete case 2";
01290         assert(upstreamStart.node()->isTextNode());
01291         TextImpl *text = static_cast<TextImpl *>(upstreamStart.node());
01292         int offset = upstreamStart.offset();
01293         // EDIT FIXME: Signed/unsigned mismatch
01294         int length = text->length();
01295         if (length == upstreamStart.offset())
01296             offset--;
01297         deleteText(text, offset, 1);
01298     }
01299     else if (downstreamStart.node()->isTextNode()) {
01300         kDebug(6200) << "start node delete case 3";
01301         TextImpl *text = static_cast<TextImpl *>(downstreamStart.node());
01302         int endOffset = text == upstreamEnd.node() ? upstreamEnd.offset() : text->length();
01303         if (endOffset > downstreamStart.offset()) {
01304             deleteText(text, downstreamStart.offset(), endOffset - downstreamStart.offset());
01305         }
01306     }
01307     else {
01308         // we have clipped the end of a non-text element
01309         // the offset must be 1 here. if it is, do nothing and move on.
01310         kDebug(6200) << "start node delete case 4";
01311         assert(downstreamStart.offset() == 1);
01312     }
01313 
01314     if (!onlyWhitespace && downstreamStart.node() != upstreamEnd.node()) {
01315         // work on intermediate nodes
01316         while (n != upstreamEnd.node()) {
01317             NodeImpl *d = n;
01318             n = n->traverseNextNode();
01319             if (d->renderer() && d->renderer()->isEditable())
01320                 removeNodeAndPrune(d, startBlock);
01321         }
01322 
01323         // work on end node
01324         assert(n == upstreamEnd.node());
01325         if (endCompletelySelected) {
01326             removeNodeAndPrune(upstreamEnd.node(), startBlock);
01327         }
01328         else if (upstreamEnd.node()->isTextNode()) {
01329             if (upstreamEnd.offset() > 0) {
01330                 TextImpl *text = static_cast<TextImpl *>(upstreamEnd.node());
01331                 deleteText(text, 0, upstreamEnd.offset());
01332             }
01333         }
01334         else {
01335             // we have clipped the beginning of a non-text element
01336             // the offset must be 0 here. if it is, do nothing and move on.
01337             assert(downstreamStart.offset() == 0);
01338         }
01339     }
01340 
01341     // Do block merge if start and end of selection are in different blocks
01342     // and the blocks are siblings. This is a first cut at this rule arrived
01343     // at by doing a bunch of edits and settling on the behavior that made
01344     // the most sense. This could change in the future as we get more
01345     // experience with how this should behave.
01346     if (startBlock != endBlock && startBlockEndBlockAreSiblings) {
01347         kDebug(6200) << "merging content to start block";
01348         NodeImpl *node = endBlock->firstChild();
01349         while (node) {
01350             NodeImpl *moveNode = node;
01351             node = node->nextSibling();
01352             removeNode(moveNode);
01353             appendNode(startBlock, moveNode);
01354         }
01355     }
01356 
01357     if (adjustEndingPositionDownstream) {
01358         kDebug(6200) << "adjust ending position downstream";
01359         endingPosition = endingPosition.equivalentDownstreamPosition();
01360     }
01361 
01362     debugPosition("ending position:     ", endingPosition);
01363     setEndingSelection(endingPosition);
01364 
01365     kDebug(6200) << "-----------------------------------------------------";
01366 }
01367 
01368 //------------------------------------------------------------------------------------------
01369 // DeleteTextCommandImpl
01370 
01371 DeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, long count)
01372     : EditCommandImpl(document), m_node(node), m_offset(offset), m_count(count)
01373 {
01374     assert(m_node);
01375     assert(m_offset >= 0);
01376     assert(m_count >= 0);
01377 
01378     m_node->ref();
01379 }
01380 
01381 DeleteTextCommandImpl::~DeleteTextCommandImpl()
01382 {
01383     if (m_node)
01384         m_node->deref();
01385 }
01386 
01387 int DeleteTextCommandImpl::commandID() const
01388 {
01389     return DeleteTextCommandID;
01390 }
01391 
01392 void DeleteTextCommandImpl::doApply()
01393 {
01394     assert(m_node);
01395 
01396     int exceptionCode = 0;
01397     m_text = m_node->substringData(m_offset, m_count, exceptionCode);
01398     assert(exceptionCode == 0);
01399 
01400     m_node->deleteData(m_offset, m_count, exceptionCode);
01401     assert(exceptionCode == 0);
01402 }
01403 
01404 void DeleteTextCommandImpl::doUnapply()
01405 {
01406     assert(m_node);
01407     assert(!m_text.isEmpty());
01408 
01409     int exceptionCode = 0;
01410     m_node->insertData(m_offset, m_text, exceptionCode);
01411     assert(exceptionCode == 0);
01412 }
01413 
01414 //------------------------------------------------------------------------------------------
01415 // InputNewlineCommandImpl
01416 
01417 InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document)
01418     : CompositeEditCommandImpl(document)
01419 {
01420 }
01421 
01422 InputNewlineCommandImpl::~InputNewlineCommandImpl()
01423 {
01424 }
01425 
01426 int InputNewlineCommandImpl::commandID() const
01427 {
01428     return InputNewlineCommandID;
01429 }
01430 
01431 void InputNewlineCommandImpl::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
01432 {
01433     // Insert the BR after the caret position. In the case the
01434     // position is a block, do an append. We don't want to insert
01435     // the BR *after* the block.
01436     Position upstream(pos.equivalentUpstreamPosition());
01437     NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
01438     if (cb == pos.node())
01439         appendNode(cb, node);
01440     else
01441         insertNodeAfter(node, pos.node());
01442 }
01443 
01444 void InputNewlineCommandImpl::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
01445 {
01446     // Insert the BR after the caret position. In the case the
01447     // position is a block, do an append. We don't want to insert
01448     // the BR *before* the block.
01449     Position upstream(pos.equivalentUpstreamPosition());
01450     NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
01451     if (cb == pos.node())
01452         appendNode(cb, node);
01453     else
01454         insertNodeBefore(node, pos.node());
01455 }
01456 
01457 void InputNewlineCommandImpl::doApply()
01458 {
01459     deleteSelection();
01460     Selection selection = endingSelection();
01461 
01462     int exceptionCode = 0;
01463     ElementImpl *breakNode = document()->createHTMLElement("BR");
01464 //     assert(exceptionCode == 0);
01465 
01466     NodeImpl *nodeToInsert = breakNode;
01467 
01468     // Handle the case where there is a typing style.
01469     if (document()->part()->editor()->typingStyle()) {
01470         int exceptionCode = 0;
01471         ElementImpl *styleElement = createTypingStyleElement();
01472         styleElement->appendChild(breakNode, exceptionCode);
01473         assert(exceptionCode == 0);
01474         nodeToInsert = styleElement;
01475     }
01476 
01477     Position pos(selection.start().equivalentDownstreamPosition());
01478     bool atStart = pos.offset() <= pos.node()->caretMinOffset();
01479     bool atEndOfBlock = pos.isLastRenderedPositionInEditableBlock();
01480 
01481     if (atEndOfBlock) {
01482         kDebug(6200) << "input newline case 1";
01483         // Insert an "extra" BR at the end of the block. This makes the "real" BR we want
01484         // to insert appear in the rendering without any significant side effects (and no
01485         // real worries either since you can't arrow past this extra one.
01486         insertNodeAfterPosition(nodeToInsert, pos);
01487         exceptionCode = 0;
01488         ElementImpl *extraBreakNode = document()->createHTMLElement("BR");
01489 //         assert(exceptionCode == 0);
01490         insertNodeAfter(extraBreakNode, nodeToInsert);
01491         setEndingSelection(Position(extraBreakNode, 0));
01492     }
01493     else if (atStart) {
01494         kDebug(6200) << "input newline case 2";
01495         // Insert node, but place the caret into index 0 of the downstream
01496         // position. This will make the caret appear after the break, and as we know
01497         // there is content at that location, this is OK.
01498         insertNodeBeforePosition(nodeToInsert, pos);
01499         setEndingSelection(Position(pos.node(), 0));
01500     }
01501     else {
01502         // Split a text node
01503         kDebug(6200) << "input newline case 3";
01504         assert(pos.node()->isTextNode());
01505         TextImpl *textNode = static_cast<TextImpl *>(pos.node());
01506         TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
01507         deleteText(textNode, 0, selection.start().offset());
01508         insertNodeBefore(textBeforeNode, textNode);
01509         insertNodeBefore(nodeToInsert, textNode);
01510         setEndingSelection(Position(textNode, 0));
01511     }
01512 }
01513 
01514 //------------------------------------------------------------------------------------------
01515 // InputTextCommandImpl
01516 
01517 InputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document)
01518     : CompositeEditCommandImpl(document), m_charactersAdded(0)
01519 {
01520 }
01521 
01522 InputTextCommandImpl::~InputTextCommandImpl()
01523 {
01524 }
01525 
01526 int InputTextCommandImpl::commandID() const
01527 {
01528     return InputTextCommandID;
01529 }
01530 
01531 void InputTextCommandImpl::doApply()
01532 {
01533 }
01534 
01535 void InputTextCommandImpl::input(const DOMString &text)
01536 {
01537     execute(text);
01538 }
01539 
01540 void InputTextCommandImpl::deleteCharacter()
01541 {
01542     assert(state() == Applied);
01543 
01544     Selection selection = endingSelection();
01545 
01546     if (!selection.start().node()->isTextNode())
01547         return;
01548 
01549     int exceptionCode = 0;
01550     int offset = selection.start().offset() - 1;
01551     if (offset >= selection.start().node()->caretMinOffset()) {
01552         TextImpl *textNode = static_cast<TextImpl *>(selection.start().node());
01553         textNode->deleteData(offset, 1, exceptionCode);
01554         assert(exceptionCode == 0);
01555         selection = Selection(Position(textNode, offset));
01556         setEndingSelection(selection);
01557         m_charactersAdded--;
01558     }
01559 }
01560 
01561 Position InputTextCommandImpl::prepareForTextInsertion(bool adjustDownstream)
01562 {
01563     // Prepare for text input by looking at the current position.
01564     // It may be necessary to insert a text node to receive characters.
01565     Selection selection = endingSelection();
01566     assert(selection.state() == Selection::CARET);
01567 
01568     Position pos = selection.start();
01569     if (adjustDownstream)
01570         pos = pos.equivalentDownstreamPosition();
01571     else
01572         pos = pos.equivalentUpstreamPosition();
01573 
01574     if (!pos.node()->isTextNode()) {
01575         NodeImpl *textNode = document()->createEditingTextNode("");
01576         NodeImpl *nodeToInsert = textNode;
01577         if (document()->part()->editor()->typingStyle()) {
01578             int exceptionCode = 0;
01579             ElementImpl *styleElement = createTypingStyleElement();
01580             styleElement->appendChild(textNode, exceptionCode);
01581             assert(exceptionCode == 0);
01582             nodeToInsert = styleElement;
01583         }
01584 
01585         // Now insert the node in the right place
01586         if (pos.node()->isEditableBlock()) {
01587             kDebug(6200) << "prepareForTextInsertion case 1";
01588             appendNode(pos.node(), nodeToInsert);
01589         }
01590         else if (pos.node()->id() == ID_BR && pos.offset() == 1) {
01591             kDebug(6200) << "prepareForTextInsertion case 2";
01592             insertNodeAfter(nodeToInsert, pos.node());
01593         }
01594         else if (pos.node()->caretMinOffset() == pos.offset()) {
01595             kDebug(6200) << "prepareForTextInsertion case 3";
01596             insertNodeBefore(nodeToInsert, pos.node());
01597         }
01598         else if (pos.node()->caretMaxOffset() == pos.offset()) {
01599             kDebug(6200) << "prepareForTextInsertion case 4";
01600             insertNodeAfter(nodeToInsert, pos.node());
01601         }
01602         else
01603             assert(false);
01604 
01605         pos = Position(textNode, 0);
01606     }
01607     else {
01608         // Handle the case where there is a typing style.
01609         if (document()->part()->editor()->typingStyle()) {
01610             if (pos.node()->isTextNode() && pos.offset() > pos.node()->caretMinOffset() && pos.offset() < pos.node()->caretMaxOffset()) {
01611                 // Need to split current text node in order to insert a span.
01612                 TextImpl *text = static_cast<TextImpl *>(pos.node());
01613                 SplitTextNodeCommand cmd(document(), text, pos.offset());
01614                 applyCommandToComposite(cmd);
01615                 setEndingSelection(Position(cmd.node(), 0));
01616             }
01617 
01618             int exceptionCode = 0;
01619             TextImpl *editingTextNode = document()->createEditingTextNode("");
01620 
01621             ElementImpl *styleElement = createTypingStyleElement();
01622             styleElement->appendChild(editingTextNode, exceptionCode);
01623             assert(exceptionCode == 0);
01624 
01625             NodeImpl *node = endingSelection().start().node();
01626             if (endingSelection().start().isLastRenderedPositionOnLine())
01627                 insertNodeAfter(styleElement, node);
01628             else
01629                 insertNodeBefore(styleElement, node);
01630             pos = Position(editingTextNode, 0);
01631         }
01632     }
01633     return pos;
01634 }
01635 
01636 void InputTextCommandImpl::execute(const DOMString &text)
01637 {
01638     Selection selection = endingSelection();
01639     bool adjustDownstream = selection.start().isFirstRenderedPositionOnLine();
01640 
01641     // Delete the current selection, or collapse whitespace, as needed
01642     if (selection.state() == Selection::RANGE)
01643         deleteSelection();
01644     else
01645         deleteCollapsibleWhitespace();
01646 
01647     // EDIT FIXME: Need to take typing style from upstream text, if any.
01648 
01649     // Make sure the document is set up to receive text
01650     Position pos = prepareForTextInsertion(adjustDownstream);
01651 
01652     TextImpl *textNode = static_cast<TextImpl *>(pos.node());
01653     long offset = pos.offset();
01654 
01655     // This is a temporary implementation for inserting adjoining spaces
01656     // into a document. We are working on a CSS-related whitespace solution
01657     // that will replace this some day.
01658     if (isWS(text))
01659         insertSpace(textNode, offset);
01660     else {
01661         const DOMString &existingText = textNode->data();
01662         if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isWS(existingText[offset - 2])) {
01663             // DOM looks like this:
01664             // character nbsp caret
01665             // As we are about to insert a non-whitespace character at the caret
01666             // convert the nbsp to a regular space.
01667             // EDIT FIXME: This needs to be improved some day to convert back only
01668             // those nbsp's added by the editor to make rendering come out right.
01669             replaceText(textNode, offset - 1, 1, " ");
01670         }
01671         insertText(textNode, offset, text);
01672     }
01673     setEndingSelection(Position(textNode, offset + text.length()));
01674     m_charactersAdded += text.length();
01675 }
01676 
01677 void InputTextCommandImpl::insertSpace(TextImpl *textNode, unsigned long offset)
01678 {
01679     assert(textNode);
01680 
01681     DOMString text(textNode->data());
01682 
01683     // count up all spaces and newlines in front of the caret
01684     // delete all collapsed ones
01685     // this will work out OK since the offset we have been passed has been upstream-ized
01686     int count = 0;
01687     for (unsigned int i = offset; i < text.length(); i++) {
01688         if (isWS(text[i]))
01689             count++;
01690         else
01691             break;
01692     }
01693     if (count > 0) {
01694         // By checking the character at the downstream position, we can
01695         // check if there is a rendered WS at the caret
01696         Position pos(textNode, offset);
01697         Position downstream = pos.equivalentDownstreamPosition();
01698         if (downstream.offset() < (long)text.length() && isWS(text[downstream.offset()]))
01699             count--; // leave this WS in
01700         if (count > 0)
01701             deleteText(textNode, offset, count);
01702     }
01703 
01704     if (offset > 0 && offset <= text.length() - 1 && !isWS(text[offset]) && !isWS(text[offset - 1])) {
01705         // insert a "regular" space
01706         insertText(textNode, offset, " ");
01707         return;
01708     }
01709 
01710     if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
01711         // DOM looks like this:
01712         // nbsp nbsp caret
01713         // insert a space between the two nbsps
01714         insertText(textNode, offset - 1, " ");
01715         return;
01716     }
01717 
01718     // insert an nbsp
01719     insertText(textNode, offset, nonBreakingSpaceString());
01720 }
01721 
01722 //------------------------------------------------------------------------------------------
01723 // InsertNodeBeforeCommandImpl
01724 
01725 InsertNodeBeforeCommandImpl::InsertNodeBeforeCommandImpl(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
01726     : EditCommandImpl(document), m_insertChild(insertChild), m_refChild(refChild)
01727 {
01728     assert(m_insertChild);
01729     m_insertChild->ref();
01730 
01731     assert(m_refChild);
01732     m_refChild->ref();
01733 }
01734 
01735 InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl()
01736 {
01737     if (m_insertChild)
01738         m_insertChild->deref();
01739     if (m_refChild)
01740         m_refChild->deref();
01741 }
01742 
01743 int InsertNodeBeforeCommandImpl::commandID() const
01744 {
01745     return InsertNodeBeforeCommandID;
01746 }
01747 
01748 void InsertNodeBeforeCommandImpl::doApply()
01749 {
01750     assert(m_insertChild);
01751     assert(m_refChild);
01752     assert(m_refChild->parentNode());
01753 
01754     int exceptionCode = 0;
01755     m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
01756     assert(exceptionCode == 0);
01757 }
01758 
01759 void InsertNodeBeforeCommandImpl::doUnapply()
01760 {
01761     assert(m_insertChild);
01762     assert(m_refChild);
01763     assert(m_refChild->parentNode());
01764 
01765     int exceptionCode = 0;
01766     m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
01767     assert(exceptionCode == 0);
01768 }
01769 
01770 //------------------------------------------------------------------------------------------
01771 // InsertTextCommandImpl
01772 
01773 InsertTextCommandImpl::InsertTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
01774     : EditCommandImpl(document), m_node(node), m_offset(offset)
01775 {
01776     assert(m_node);
01777     assert(m_offset >= 0);
01778     assert(text.length() > 0);
01779 
01780     m_node->ref();
01781     m_text = text.copy(); // make a copy to ensure that the string never changes
01782 }
01783 
01784 InsertTextCommandImpl::~InsertTextCommandImpl()
01785 {
01786     if (m_node)
01787         m_node->deref();
01788 }
01789 
01790 int InsertTextCommandImpl::commandID() const
01791 {
01792     return InsertTextCommandID;
01793 }
01794 
01795 void InsertTextCommandImpl::doApply()
01796 {
01797     assert(m_node);
01798     assert(!m_text.isEmpty());
01799 
01800     int exceptionCode = 0;
01801     m_node->insertData(m_offset, m_text, exceptionCode);
01802     assert(exceptionCode == 0);
01803 }
01804 
01805 void InsertTextCommandImpl::doUnapply()
01806 {
01807     assert(m_node);
01808     assert(!m_text.isEmpty());
01809 
01810     int exceptionCode = 0;
01811     m_node->deleteData(m_offset, m_text.length(), exceptionCode);
01812     assert(exceptionCode == 0);
01813 }
01814 
01815 //------------------------------------------------------------------------------------------
01816 // JoinTextNodesCommandImpl
01817 
01818 JoinTextNodesCommandImpl::JoinTextNodesCommandImpl(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
01819     : EditCommandImpl(document), m_text1(text1), m_text2(text2)
01820 {
01821     assert(m_text1);
01822     assert(m_text2);
01823     assert(m_text1->nextSibling() == m_text2);
01824     assert(m_text1->length() > 0);
01825     assert(m_text2->length() > 0);
01826 
01827     m_text1->ref();
01828     m_text2->ref();
01829 }
01830 
01831 JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl()
01832 {
01833     if (m_text1)
01834         m_text1->deref();
01835     if (m_text2)
01836         m_text2->deref();
01837 }
01838 
01839 int JoinTextNodesCommandImpl::commandID() const
01840 {
01841     return JoinTextNodesCommandID;
01842 }
01843 
01844 void JoinTextNodesCommandImpl::doApply()
01845 {
01846     assert(m_text1);
01847     assert(m_text2);
01848     assert(m_text1->nextSibling() == m_text2);
01849 
01850     int exceptionCode = 0;
01851     m_text2->insertData(0, m_text1->data(), exceptionCode);
01852     assert(exceptionCode == 0);
01853 
01854     m_text2->parentNode()->removeChild(m_text1, exceptionCode);
01855     assert(exceptionCode == 0);
01856 
01857     m_offset = m_text1->length();
01858 }
01859 
01860 void JoinTextNodesCommandImpl::doUnapply()
01861 {
01862     assert(m_text2);
01863     assert(m_offset > 0);
01864 
01865     int exceptionCode = 0;
01866 
01867     m_text2->deleteData(0, m_offset, exceptionCode);
01868     assert(exceptionCode == 0);
01869 
01870     m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
01871     assert(exceptionCode == 0);
01872 
01873     assert(m_text2->previousSibling()->isTextNode());
01874     assert(m_text2->previousSibling() == m_text1);
01875 }
01876 
01877 //------------------------------------------------------------------------------------------
01878 // ReplaceSelectionCommandImpl
01879 
01880 ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement)
01881     : CompositeEditCommandImpl(document), m_fragment(fragment), m_selectReplacement(selectReplacement)
01882 {
01883 }
01884 
01885 ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl()
01886 {
01887 }
01888 
01889 int ReplaceSelectionCommandImpl::commandID() const
01890 {
01891     return ReplaceSelectionCommandID;
01892 }
01893 
01894 void ReplaceSelectionCommandImpl::doApply()
01895 {
01896     NodeImpl *firstChild = m_fragment->firstChild();
01897     NodeImpl *lastChild = m_fragment->lastChild();
01898 
01899     Selection selection = endingSelection();
01900 
01901     // Delete the current selection, or collapse whitespace, as needed
01902     if (selection.state() == Selection::RANGE)
01903         deleteSelection();
01904     else
01905         deleteCollapsibleWhitespace();
01906 
01907     selection = endingSelection();
01908     assert(!selection.isEmpty());
01909 
01910     if (!firstChild) {
01911         // Pasting something that didn't parse or was empty.
01912         assert(!lastChild);
01913     } else if (firstChild == lastChild && firstChild->isTextNode()) {
01914         // Simple text paste. Treat as if the text were typed.
01915         Position base = selection.base();
01916         inputText(static_cast<TextImpl *>(firstChild)->data());
01917         if (m_selectReplacement) {
01918             setEndingSelection(Selection(base, endingSelection().extent()));
01919         }
01920     }
01921     else {
01922         // HTML fragment paste.
01923         NodeImpl *beforeNode = firstChild;
01924         NodeImpl *node = firstChild->nextSibling();
01925 
01926         insertNodeAt(firstChild, selection.start().node(), selection.start().offset());
01927 
01928         // Insert the nodes from the fragment
01929         while (node) {
01930             NodeImpl *next = node->nextSibling();
01931             insertNodeAfter(node, beforeNode);
01932             beforeNode = node;
01933             node = next;
01934         }
01935         assert(beforeNode);
01936 
01937         // Find the last leaf.
01938         NodeImpl *lastLeaf = lastChild;
01939         while (1) {
01940             NodeImpl *nextChild = lastLeaf->lastChild();
01941             if (!nextChild)
01942                 break;
01943             lastLeaf = nextChild;
01944         }
01945 
01946     if (m_selectReplacement) {
01947             // Find the first leaf.
01948             NodeImpl *firstLeaf = firstChild;
01949             while (1) {
01950                 NodeImpl *nextChild = firstLeaf->firstChild();
01951                 if (!nextChild)
01952                     break;
01953                 firstLeaf = nextChild;
01954             }
01955             // Select what was inserted.
01956             setEndingSelection(Selection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset())));
01957         } else {
01958             // Place the cursor after what was inserted.
01959             setEndingSelection(Position(lastLeaf, lastLeaf->caretMaxOffset()));
01960         }
01961     }
01962 }
01963 
01964 //------------------------------------------------------------------------------------------
01965 // MoveSelectionCommandImpl
01966 
01967 MoveSelectionCommandImpl::MoveSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position)
01968 : CompositeEditCommandImpl(document), m_fragment(fragment), m_position(position)
01969 {
01970 }
01971 
01972 MoveSelectionCommandImpl::~MoveSelectionCommandImpl()
01973 {
01974 }
01975 
01976 int MoveSelectionCommandImpl::commandID() const
01977 {
01978     return MoveSelectionCommandID;
01979 }
01980 
01981 void MoveSelectionCommandImpl::doApply()
01982 {
01983     Selection selection = endingSelection();
01984     assert(selection.state() == Selection::RANGE);
01985 
01986     // Update the position otherwise it may become invalid after the selection is deleted.
01987     NodeImpl *positionNode = m_position.node();
01988     long positionOffset = m_position.offset();
01989     Position selectionEnd = selection.end();
01990     long selectionEndOffset = selectionEnd.offset();
01991     if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) {
01992         positionOffset -= selectionEndOffset;
01993         Position selectionStart = selection.start();
01994         if (selectionStart.node() == positionNode) {
01995             positionOffset += selectionStart.offset();
01996         }
01997     }
01998 
01999     deleteSelection();
02000 
02001     setEndingSelection(Position(positionNode, positionOffset));
02002     ReplaceSelectionCommand cmd(document(), m_fragment, true);
02003     applyCommandToComposite(cmd);
02004 }
02005 
02006 //------------------------------------------------------------------------------------------
02007 // RemoveCSSPropertyCommandImpl
02008 
02009 RemoveCSSPropertyCommandImpl::RemoveCSSPropertyCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property)
02010     : EditCommandImpl(document), m_decl(decl), m_property(property), m_important(false)
02011 {
02012     assert(m_decl);
02013     m_decl->ref();
02014 }
02015 
02016 RemoveCSSPropertyCommandImpl::~RemoveCSSPropertyCommandImpl()
02017 {
02018     assert(m_decl);
02019     m_decl->deref();
02020 }
02021 
02022 int RemoveCSSPropertyCommandImpl::commandID() const
02023 {
02024     return RemoveCSSPropertyCommandID;
02025 }
02026 
02027 void RemoveCSSPropertyCommandImpl::doApply()
02028 {
02029     assert(m_decl);
02030 
02031     m_oldValue = m_decl->getPropertyValue(m_property);
02032     assert(!m_oldValue.isNull());
02033 
02034     m_important = m_decl->getPropertyPriority(m_property);
02035     m_decl->removeProperty(m_property);
02036 }
02037 
02038 void RemoveCSSPropertyCommandImpl::doUnapply()
02039 {
02040     assert(m_decl);
02041     assert(!m_oldValue.isNull());
02042 
02043     m_decl->setProperty(m_property, m_oldValue, m_important);
02044 }
02045 
02046 //------------------------------------------------------------------------------------------
02047 // RemoveNodeAttributeCommandImpl
02048 
02049 RemoveNodeAttributeCommandImpl::RemoveNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute)
02050     : EditCommandImpl(document), m_element(element), m_attribute(attribute)
02051 {
02052     assert(m_element);
02053     m_element->ref();
02054 }
02055 
02056 RemoveNodeAttributeCommandImpl::~RemoveNodeAttributeCommandImpl()
02057 {
02058     assert(m_element);
02059     m_element->deref();
02060 }
02061 
02062 int RemoveNodeAttributeCommandImpl::commandID() const
02063 {
02064     return RemoveNodeAttributeCommandID;
02065 }
02066 
02067 void RemoveNodeAttributeCommandImpl::doApply()
02068 {
02069     assert(m_element);
02070 
02071     m_oldValue = m_element->getAttribute(m_attribute);
02072     assert(!m_oldValue.isNull());
02073 
02074     int exceptionCode = 0;
02075     m_element->removeAttribute(m_attribute, exceptionCode);
02076     assert(exceptionCode == 0);
02077 }
02078 
02079 void RemoveNodeAttributeCommandImpl::doUnapply()
02080 {
02081     assert(m_element);
02082     assert(!m_oldValue.isNull());
02083 
02084 //     int exceptionCode = 0;
02085     m_element->setAttribute(m_attribute, m_oldValue.implementation());
02086 //     assert(exceptionCode == 0);
02087 }
02088 
02089 //------------------------------------------------------------------------------------------
02090 // RemoveNodeCommandImpl
02091 
02092 RemoveNodeCommandImpl::RemoveNodeCommandImpl(DocumentImpl *document, NodeImpl *removeChild)
02093     : EditCommandImpl(document), m_parent(0), m_removeChild(removeChild), m_refChild(0)
02094 {
02095     assert(m_removeChild);
02096     m_removeChild->ref();
02097 
02098     m_parent = m_removeChild->parentNode();
02099     assert(m_parent);
02100     m_parent->ref();
02101 
02102     NodeListImpl *children = m_parent->childNodes();
02103     for (int i = children->length(); i >= 0; i--) {
02104         NodeImpl *node = children->item(i);
02105         if (node == m_removeChild)
02106             break;
02107         m_refChild = node;
02108     }
02109 
02110     if (m_refChild)
02111         m_refChild->ref();
02112 }
02113 
02114 RemoveNodeCommandImpl::~RemoveNodeCommandImpl()
02115 {
02116     if (m_parent)
02117         m_parent->deref();
02118     if (m_removeChild)
02119         m_removeChild->deref();
02120     if (m_refChild)
02121         m_refChild->deref();
02122 }
02123 
02124 int RemoveNodeCommandImpl::commandID() const
02125 {
02126     return RemoveNodeCommandID;
02127 }
02128 
02129 void RemoveNodeCommandImpl::doApply()
02130 {
02131     assert(m_parent);
02132     assert(m_removeChild);
02133 
02134     int exceptionCode = 0;
02135     m_parent->removeChild(m_removeChild, exceptionCode);
02136     assert(exceptionCode == 0);
02137 }
02138 
02139 void RemoveNodeCommandImpl::doUnapply()
02140 {
02141     assert(m_parent);
02142     assert(m_removeChild);
02143 
02144     int exceptionCode = 0;
02145     if (m_refChild)
02146         m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
02147     else
02148         m_parent->appendChild(m_removeChild, exceptionCode);
02149     assert(exceptionCode == 0);
02150 }
02151 
02152 //------------------------------------------------------------------------------------------
02153 // RemoveNodeAndPruneCommandImpl
02154 
02155 RemoveNodeAndPruneCommandImpl::RemoveNodeAndPruneCommandImpl(DocumentImpl *document, NodeImpl *pruneNode, NodeImpl *stopNode)
02156     : CompositeEditCommandImpl(document), m_pruneNode(pruneNode), m_stopNode(stopNode)
02157 {
02158     assert(m_pruneNode);
02159     m_pruneNode->ref();
02160     if (m_stopNode)
02161         m_stopNode->ref();
02162 }
02163 
02164 RemoveNodeAndPruneCommandImpl::~RemoveNodeAndPruneCommandImpl()
02165 {
02166     m_pruneNode->deref();
02167     if (m_stopNode)
02168         m_stopNode->deref();
02169 }
02170 
02171 int RemoveNodeAndPruneCommandImpl::commandID() const
02172 {
02173     return RemoveNodeAndPruneCommandID;
02174 }
02175 
02176 void RemoveNodeAndPruneCommandImpl::doApply()
02177 {
02178     NodeImpl *editableBlock = m_pruneNode->enclosingBlockFlowElement();
02179     NodeImpl *pruneNode = m_pruneNode;
02180     NodeImpl *node = pruneNode->traversePreviousNode();
02181     removeNode(pruneNode);
02182     while (1) {
02183         if (node == m_stopNode || editableBlock != node->enclosingBlockFlowElement() || !shouldPruneNode(node))
02184             break;
02185         pruneNode = node;
02186         node = node->traversePreviousNode();
02187         removeNode(pruneNode);
02188     }
02189 }
02190 
02191 //------------------------------------------------------------------------------------------
02192 // RemoveNodePreservingChildrenCommandImpl
02193 
02194 RemoveNodePreservingChildrenCommandImpl::RemoveNodePreservingChildrenCommandImpl(DocumentImpl *document, NodeImpl *node)
02195     : CompositeEditCommandImpl(document), m_node(node)
02196 {
02197     assert(m_node);
02198     m_node->ref();
02199 }
02200 
02201 RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl()
02202 {
02203     if (m_node)
02204         m_node->deref();
02205 }
02206 
02207 int RemoveNodePreservingChildrenCommandImpl::commandID() const
02208 {
02209     return RemoveNodePreservingChildrenCommandID;
02210 }
02211 
02212 void RemoveNodePreservingChildrenCommandImpl::doApply()
02213 {
02214     NodeListImpl *children = node()->childNodes();
02215     int length = children->length();
02216     for (int i = 0; i < length; i++) {
02217         NodeImpl *child = children->item(0);
02218         removeNode(child);
02219         insertNodeBefore(child, node());
02220     }
02221     removeNode(node());
02222 }
02223 
02224 //------------------------------------------------------------------------------------------
02225 // SetNodeAttributeCommandImpl
02226 
02227 SetNodeAttributeCommandImpl::SetNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value)
02228     : EditCommandImpl(document), m_element(element), m_attribute(attribute), m_value(value)
02229 {
02230     assert(m_element);
02231     m_element->ref();
02232     assert(!m_value.isNull());
02233 }
02234 
02235 SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl()
02236 {
02237     if (m_element)
02238         m_element->deref();
02239 }
02240 
02241 int SetNodeAttributeCommandImpl::commandID() const
02242 {
02243     return SetNodeAttributeCommandID;
02244 }
02245 
02246 void SetNodeAttributeCommandImpl::doApply()
02247 {
02248     assert(m_element);
02249     assert(!m_value.isNull());
02250 
02251 //     int exceptionCode = 0;
02252     m_oldValue = m_element->getAttribute(m_attribute);
02253     m_element->setAttribute(m_attribute, m_value.implementation());
02254 //     assert(exceptionCode == 0);
02255 }
02256 
02257 void SetNodeAttributeCommandImpl::doUnapply()
02258 {
02259     assert(m_element);
02260     assert(!m_oldValue.isNull());
02261 
02262 //     int exceptionCode = 0;
02263     m_element->setAttribute(m_attribute, m_oldValue.implementation());
02264 //     assert(exceptionCode == 0);
02265 }
02266 
02267 //------------------------------------------------------------------------------------------
02268 // SplitTextNodeCommandImpl
02269 
02270 SplitTextNodeCommandImpl::SplitTextNodeCommandImpl(DocumentImpl *document, TextImpl *text, long offset)
02271     : EditCommandImpl(document), m_text1(0), m_text2(text), m_offset(offset)
02272 {
02273     assert(m_text2);
02274     assert(m_text2->length() > 0);
02275 
02276     m_text2->ref();
02277 }
02278 
02279 SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl()
02280 {
02281     if (m_text1)
02282         m_text1->deref();
02283     if (m_text2)
02284         m_text2->deref();
02285 }
02286 
02287 int SplitTextNodeCommandImpl::commandID() const
02288 {
02289     return SplitTextNodeCommandID;
02290 }
02291 
02292 void SplitTextNodeCommandImpl::doApply()
02293 {
02294     assert(m_text2);
02295     assert(m_offset > 0);
02296 
02297     int exceptionCode = 0;
02298 
02299     // EDIT FIXME: This should use better smarts for figuring out which portion
02300     // of the split to copy (based on their comparative sizes). We should also
02301     // just use the DOM's splitText function.
02302 
02303     if (!m_text1) {
02304         // create only if needed.
02305         // if reapplying, this object will already exist.
02306         m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode));
02307         assert(exceptionCode == 0);
02308         assert(m_text1);
02309         m_text1->ref();
02310     }
02311 
02312     m_text2->deleteData(0, m_offset, exceptionCode);
02313     assert(exceptionCode == 0);
02314 
02315     m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
02316     assert(exceptionCode == 0);
02317 
02318     assert(m_text2->previousSibling()->isTextNode());
02319     assert(m_text2->previousSibling() == m_text1);
02320 }
02321 
02322 void SplitTextNodeCommandImpl::doUnapply()
02323 {
02324     assert(m_text1);
02325     assert(m_text2);
02326 
02327     assert(m_text1->nextSibling() == m_text2);
02328 
02329     int exceptionCode = 0;
02330     m_text2->insertData(0, m_text1->data(), exceptionCode);
02331     assert(exceptionCode == 0);
02332 
02333     m_text2->parentNode()->removeChild(m_text1, exceptionCode);
02334     assert(exceptionCode == 0);
02335 
02336     m_offset = m_text1->length();
02337 }
02338 
02339 //------------------------------------------------------------------------------------------
02340 // TypingCommandImpl
02341 
02342 TypingCommandImpl::TypingCommandImpl(DocumentImpl *document)
02343     : CompositeEditCommandImpl(document), m_openForMoreTyping(true)
02344 {
02345 }
02346 
02347 TypingCommandImpl::~TypingCommandImpl()
02348 {
02349 }
02350 
02351 int TypingCommandImpl::commandID() const
02352 {
02353     return TypingCommandID;
02354 }
02355 
02356 void TypingCommandImpl::doApply()
02357 {
02358 }
02359 
02360 void TypingCommandImpl::typingAddedToOpenCommand()
02361 {
02362     assert(document());
02363     assert(document()->part());
02364     EditCommand cmd(this);
02365     document()->part()->editor()->appliedEditing(cmd);
02366 }
02367 
02368 void TypingCommandImpl::insertText(const DOMString &text)
02369 {
02370     if (document()->part()->editor()->typingStyle() || m_cmds.count() == 0) {
02371         InputTextCommand cmd(document());
02372         applyCommandToComposite(cmd);
02373         cmd.input(text);
02374     }
02375     else {
02376         EditCommand lastCommand = m_cmds.last();
02377         if (lastCommand.commandID() == InputTextCommandID) {
02378             static_cast<InputTextCommand &>(lastCommand).input(text);
02379         }
02380         else {
02381             InputTextCommand cmd(document());
02382             applyCommandToComposite(cmd);
02383             cmd.input(text);
02384         }
02385     }
02386     typingAddedToOpenCommand();
02387 }
02388 
02389 void TypingCommandImpl::insertNewline()
02390 {
02391     InputNewlineCommand cmd(document());
02392     applyCommandToComposite(cmd);
02393     typingAddedToOpenCommand();
02394 }
02395 
02396 void TypingCommandImpl::issueCommandForDeleteKey()
02397 {
02398     Selection selectionToDelete = endingSelection();
02399     assert(selectionToDelete.state() != Selection::NONE);
02400 
02401     if (selectionToDelete.state() == Selection::CARET) {
02402         Position pos(selectionToDelete.start());
02403         if (pos.inFirstEditableInRootEditableElement() && pos.offset() <= pos.node()->caretMinOffset()) {
02404             // we're at the start of a root editable block...do nothing
02405             return;
02406         }
02407         selectionToDelete = Selection(pos.previousCharacterPosition(), pos);
02408     }
02409     deleteSelection(selectionToDelete);
02410     typingAddedToOpenCommand();
02411 }
02412 
02413 void TypingCommandImpl::deleteKeyPressed()
02414 {
02415 // EDIT FIXME: The ifdef'ed out code below should be re-enabled.
02416 // In order for this to happen, the deleteCharacter case
02417 // needs work. Specifically, the caret-positioning code
02418 // and whitespace-handling code in DeleteSelectionCommandImpl::doApply()
02419 // needs to be factored out so it can be used again here.
02420 // Until that work is done, issueCommandForDeleteKey() does the
02421 // right thing, but less efficiently and with the cost of more
02422 // objects.
02423     issueCommandForDeleteKey();
02424 #if 0
02425     if (m_cmds.count() == 0) {
02426         issueCommandForDeleteKey();
02427     }
02428     else {
02429         EditCommand lastCommand = m_cmds.last();
02430         if (lastCommand.commandID() == InputTextCommandID) {
02431             InputTextCommand cmd = static_cast<InputTextCommand &>(lastCommand);
02432             cmd.deleteCharacter();
02433             if (cmd.charactersAdded() == 0) {
02434                 removeCommand(cmd);
02435             }
02436         }
02437         else if (lastCommand.commandID() == InputNewlineCommandID) {
02438             lastCommand.unapply();
02439             removeCommand(lastCommand);
02440         }
02441         else {
02442             issueCommandForDeleteKey();
02443         }
02444     }
02445 #endif
02446 }
02447 
02448 void TypingCommandImpl::removeCommand(const EditCommand &cmd)
02449 {
02450     // NOTE: If the passed-in command is the last command in the
02451     // composite, we could remove all traces of this typing command
02452     // from the system, including the undo chain. Other editors do
02453     // not do this, but we could.
02454 
02455     m_cmds.removeAll(cmd);
02456     if (m_cmds.count() == 0)
02457         setEndingSelection(startingSelection());
02458     else
02459         setEndingSelection(m_cmds.last().endingSelection());
02460 }
02461 
02462 //------------------------------------------------------------------------------------------
02463 
02464 } // namespace khtml

KHTML

Skip menu "KHTML"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal