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

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
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
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