fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / khtml / editing / htmlediting_impl.cpp
blobe0b2d2bf7728bf8a37f4fa5880612678716cf5b6
1 /*
2 * Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 #include "htmlediting_impl.h"
27 #include "editor.h"
29 #include "css/cssproperties.h"
30 #include "css/css_valueimpl.h"
31 #include "dom/css_value.h"
32 #include "html/html_elementimpl.h"
33 #include "html/html_imageimpl.h"
34 #include "misc/htmlattrs.h"
35 #include "misc/htmltags.h"
36 #include "rendering/render_object.h"
37 #include "rendering/render_style.h"
38 #include "rendering/render_text.h"
39 #include "xml/dom_docimpl.h"
40 #include "xml/dom_elementimpl.h"
41 #include "xml/dom_position.h"
42 #include "xml/dom_positioniterator.h"
43 #include "xml/dom_nodeimpl.h"
44 #include "xml/dom_selection.h"
45 #include "xml/dom_stringimpl.h"
46 #include "xml/dom_textimpl.h"
47 #include "xml/dom2_rangeimpl.h"
48 #include "xml/dom2_viewsimpl.h"
50 #include "khtml_part.h"
51 #include "khtmlview.h"
53 #include <QList>
54 #include <limits.h>
56 using DOM::AttrImpl;
57 using DOM::CSSPrimitiveValue;
58 using DOM::CSSPrimitiveValueImpl;
59 using DOM::CSSProperty;
60 using DOM::CSSStyleDeclarationImpl;
61 using DOM::CSSValueImpl;
62 using DOM::DocumentFragmentImpl;
63 using DOM::DocumentImpl;
64 using DOM::DOMString;
65 using DOM::DOMStringImpl;
66 using DOM::EditingTextImpl;
67 using DOM::PositionIterator;
68 using DOM::ElementImpl;
69 using DOM::HTMLElementImpl;
70 using DOM::HTMLImageElementImpl;
71 using DOM::NamedAttrMapImpl;
72 using DOM::Node;
73 using DOM::NodeImpl;
74 using DOM::NodeListImpl;
75 using DOM::Position;
76 using DOM::Range;
77 using DOM::RangeImpl;
78 using DOM::Selection;
79 using DOM::TextImpl;
80 using DOM::TreeWalkerImpl;
82 #ifdef LOG_DISABLED
83 #define debugPosition(a,b) ((void)0)
84 #endif
86 namespace khtml {
89 static inline bool isNBSP(const QChar &c)
91 return c == QChar(0xa0);
94 static inline bool isWS(const QChar &c)
96 return c.isSpace() && c != QChar(0xa0);
99 static inline bool isWS(const DOMString &text)
101 if (text.length() != 1)
102 return false;
104 return isWS(text[0]);
107 static inline bool isWS(const Position &pos)
109 if (!pos.node())
110 return false;
112 if (!pos.node()->isTextNode())
113 return false;
115 const DOMString &string = static_cast<TextImpl *>(pos.node())->data();
116 return isWS(string[pos.offset()]);
119 static bool shouldPruneNode(NodeImpl *node)
121 if (!node)
122 return false;
124 RenderObject *renderer = node->renderer();
125 if (!renderer)
126 return true;
128 if (node->hasChildNodes())
129 return false;
131 if (node->rootEditableElement() == node)
132 return false;
134 if (renderer->isBR() || renderer->isReplaced())
135 return false;
137 if (node->isTextNode()) {
138 TextImpl *text = static_cast<TextImpl *>(node);
139 if (text->length() == 0)
140 return true;
141 return false;
144 if (!node->isHTMLElement()/* && !node->isXMLElementNode()*/)
145 return false;
147 if (node->id() == ID_BODY)
148 return false;
150 if (!node->isContentEditable())
151 return false;
153 return true;
156 static Position leadingWhitespacePosition(const Position &pos)
158 assert(pos.notEmpty());
160 Selection selection(pos);
161 Position prev = pos.previousCharacterPosition();
162 if (prev != pos && prev.node()->inSameContainingBlockFlowElement(pos.node()) && prev.node()->isTextNode()) {
163 DOMString string = static_cast<TextImpl *>(prev.node())->data();
164 if (isWS(string[prev.offset()]))
165 return prev;
168 return Position();
171 static Position trailingWhitespacePosition(const Position &pos)
173 assert(pos.notEmpty());
175 if (pos.node()->isTextNode()) {
176 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
177 if (pos.offset() >= (long)textNode->length()) {
178 Position next = pos.nextCharacterPosition();
179 if (next != pos && next.node()->inSameContainingBlockFlowElement(pos.node()) && next.node()->isTextNode()) {
180 DOMString string = static_cast<TextImpl *>(next.node())->data();
181 if (isWS(string[0]))
182 return next;
185 else {
186 DOMString string = static_cast<TextImpl *>(pos.node())->data();
187 if (isWS(string[pos.offset()]))
188 return pos;
192 return Position();
195 static bool textNodesAreJoinable(TextImpl *text1, TextImpl *text2)
197 assert(text1);
198 assert(text2);
200 return (text1->nextSibling() == text2);
203 static DOMString &nonBreakingSpaceString()
205 static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
206 return nonBreakingSpaceString;
209 static DOMString &styleSpanClassString()
211 static DOMString styleSpanClassString = "khtml-style-span";
212 return styleSpanClassString;
215 #ifndef LOG_DISABLED
216 static void debugPosition(const char *prefix, const Position &pos)
218 kDebug(6200) << prefix << DOM::getPrintableName(pos.node()->id()) << pos.node() << pos.offset();
220 #endif
222 //------------------------------------------------------------------------------------------
223 // EditCommandImpl
225 EditCommandImpl::EditCommandImpl(DocumentImpl *document)
226 : SharedCommandImpl(), m_document(document), m_state(NotApplied), m_parent(0)
228 assert(m_document);
229 assert(m_document->part());
230 m_document->ref();
231 m_startingSelection = m_document->part()->caret();
232 m_endingSelection = m_startingSelection;
235 EditCommandImpl::~EditCommandImpl()
237 m_document->deref();
240 int EditCommandImpl::commandID() const
242 return EditCommandID;
245 void EditCommandImpl::apply()
247 assert(m_document);
248 assert(m_document->part());
249 assert(state() == NotApplied);
251 doApply();
253 m_state = Applied;
255 if (!isCompositeStep()) {
256 EditCommand cmd(this);
257 m_document->part()->editor()->appliedEditing(cmd);
261 void EditCommandImpl::unapply()
263 assert(m_document);
264 assert(m_document->part());
265 assert(state() == Applied);
267 doUnapply();
269 m_state = NotApplied;
271 if (!isCompositeStep()) {
272 EditCommand cmd(this);
273 m_document->part()->editor()->unappliedEditing(cmd);
277 void EditCommandImpl::reapply()
279 assert(m_document);
280 assert(m_document->part());
281 assert(state() == NotApplied);
283 doReapply();
285 m_state = Applied;
287 if (!isCompositeStep()) {
288 EditCommand cmd(this);
289 m_document->part()->editor()->reappliedEditing(cmd);
293 void EditCommandImpl::doReapply()
295 doApply();
298 void EditCommandImpl::setStartingSelection(const Selection &s)
300 m_startingSelection = s;
301 EditCommand cmd( parent() );
302 while (cmd.notNull()) {
303 cmd.handle()->m_startingSelection = s;
304 cmd = cmd.handle()->parent();
308 void EditCommandImpl::setEndingSelection(const Selection &s)
310 m_endingSelection = s;
311 EditCommand cmd = parent();
312 while (cmd.notNull()) {
313 cmd.handle()->m_endingSelection = s;
314 cmd = cmd.handle()->parent();
318 EditCommandImpl* EditCommandImpl::parent() const
320 return m_parent;
323 void EditCommandImpl::setParent(EditCommandImpl* cmd)
325 m_parent = cmd;
328 //------------------------------------------------------------------------------------------
329 // CompositeEditCommandImpl
331 CompositeEditCommandImpl::CompositeEditCommandImpl(DocumentImpl *document)
332 : EditCommandImpl(document)
336 CompositeEditCommandImpl::~CompositeEditCommandImpl()
340 int CompositeEditCommandImpl::commandID() const
342 return CompositeEditCommandID;
345 void CompositeEditCommandImpl::doUnapply()
347 if (m_cmds.count() == 0) {
348 return;
351 for (int i = m_cmds.count() - 1; i >= 0; --i)
352 m_cmds[i].unapply();
354 setState(NotApplied);
357 void CompositeEditCommandImpl::doReapply()
359 if (m_cmds.count() == 0) {
360 return;
362 QMutableListIterator<EditCommand> it(m_cmds);
363 while (it.hasNext())
364 it.next().reapply();
366 setState(Applied);
370 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
372 void CompositeEditCommandImpl::applyCommandToComposite(EditCommand &cmd)
374 cmd.setStartingSelection(endingSelection());//###?
375 cmd.setEndingSelection(endingSelection());
376 cmd.handle()->setParent(this);
377 cmd.apply();
378 m_cmds.append(cmd);
381 void CompositeEditCommandImpl::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
383 InsertNodeBeforeCommand cmd(document(), insertChild, refChild);
384 applyCommandToComposite(cmd);
387 void CompositeEditCommandImpl::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
389 if (refChild->parentNode()->lastChild() == refChild) {
390 appendNode(refChild->parentNode(), insertChild);
392 else {
393 assert(refChild->nextSibling());
394 insertNodeBefore(insertChild, refChild->nextSibling());
398 void CompositeEditCommandImpl::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
400 if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
401 NodeImpl *child = refChild->firstChild();
402 for (long i = 0; child && i < offset; i++)
403 child = child->nextSibling();
404 if (child)
405 insertNodeBefore(insertChild, child);
406 else
407 appendNode(refChild, insertChild);
409 else if (refChild->caretMinOffset() >= offset) {
410 insertNodeBefore(insertChild, refChild);
412 else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
413 splitTextNode(static_cast<TextImpl *>(refChild), offset);
414 insertNodeBefore(insertChild, refChild);
416 else {
417 insertNodeAfter(insertChild, refChild);
421 void CompositeEditCommandImpl::appendNode(NodeImpl *parent, NodeImpl *appendChild)
423 AppendNodeCommand cmd(document(), parent, appendChild);
424 applyCommandToComposite(cmd);
427 void CompositeEditCommandImpl::removeNode(NodeImpl *removeChild)
429 RemoveNodeCommand cmd(document(), removeChild);
430 applyCommandToComposite(cmd);
433 void CompositeEditCommandImpl::removeNodeAndPrune(NodeImpl *pruneNode, NodeImpl *stopNode)
435 RemoveNodeAndPruneCommand cmd(document(), pruneNode, stopNode);
436 applyCommandToComposite(cmd);
439 void CompositeEditCommandImpl::removeNodePreservingChildren(NodeImpl *removeChild)
441 RemoveNodePreservingChildrenCommand cmd(document(), removeChild);
442 applyCommandToComposite(cmd);
445 void CompositeEditCommandImpl::splitTextNode(TextImpl *text, long offset)
447 SplitTextNodeCommand cmd(document(), text, offset);
448 applyCommandToComposite(cmd);
451 void CompositeEditCommandImpl::joinTextNodes(TextImpl *text1, TextImpl *text2)
453 JoinTextNodesCommand cmd(document(), text1, text2);
454 applyCommandToComposite(cmd);
457 void CompositeEditCommandImpl::inputText(const DOMString &text)
459 InputTextCommand cmd(document());
460 applyCommandToComposite(cmd);
461 cmd.input(text);
464 void CompositeEditCommandImpl::insertText(TextImpl *node, long offset, const DOMString &text)
466 InsertTextCommand cmd(document(), node, offset, text);
467 applyCommandToComposite(cmd);
470 void CompositeEditCommandImpl::deleteText(TextImpl *node, long offset, long count)
472 DeleteTextCommand cmd(document(), node, offset, count);
473 applyCommandToComposite(cmd);
476 void CompositeEditCommandImpl::replaceText(TextImpl *node, long offset, long count, const DOMString &replacementText)
478 DeleteTextCommand deleteCommand(document(), node, offset, count);
479 applyCommandToComposite(deleteCommand);
480 InsertTextCommand insertCommand(document(), node, offset, replacementText);
481 applyCommandToComposite(insertCommand);
484 void CompositeEditCommandImpl::deleteSelection()
486 if (endingSelection().state() == Selection::RANGE) {
487 DeleteSelectionCommand cmd(document());
488 applyCommandToComposite(cmd);
492 void CompositeEditCommandImpl::deleteSelection(const Selection &selection)
494 if (selection.state() == Selection::RANGE) {
495 DeleteSelectionCommand cmd(document(), selection);
496 applyCommandToComposite(cmd);
500 void CompositeEditCommandImpl::deleteCollapsibleWhitespace()
502 DeleteCollapsibleWhitespaceCommand cmd(document());
503 applyCommandToComposite(cmd);
506 void CompositeEditCommandImpl::deleteCollapsibleWhitespace(const Selection &selection)
508 DeleteCollapsibleWhitespaceCommand cmd(document(), selection);
509 applyCommandToComposite(cmd);
512 void CompositeEditCommandImpl::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
514 RemoveCSSPropertyCommand cmd(document(), decl, property);
515 applyCommandToComposite(cmd);
518 void CompositeEditCommandImpl::removeNodeAttribute(ElementImpl *element, int attribute)
520 RemoveNodeAttributeCommand cmd(document(), element, attribute);
521 applyCommandToComposite(cmd);
524 void CompositeEditCommandImpl::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
526 SetNodeAttributeCommand cmd(document(), element, attribute, value);
527 applyCommandToComposite(cmd);
530 ElementImpl *CompositeEditCommandImpl::createTypingStyleElement() const
532 int exceptionCode = 0;
533 ElementImpl *styleElement = document()->createHTMLElement("SPAN");
534 // assert(exceptionCode == 0);
536 styleElement->setAttribute(ATTR_STYLE, document()->part()->editor()->typingStyle()->cssText().implementation());
537 // assert(exceptionCode == 0);
539 styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
540 assert(exceptionCode == 0);
542 return styleElement;
545 //==========================================================================================
546 // Concrete commands
547 //------------------------------------------------------------------------------------------
548 // AppendNodeCommandImpl
550 AppendNodeCommandImpl::AppendNodeCommandImpl(DocumentImpl *document, NodeImpl *parentNode, NodeImpl *appendChild)
551 : EditCommandImpl(document), m_parentNode(parentNode), m_appendChild(appendChild)
553 assert(m_parentNode);
554 m_parentNode->ref();
556 assert(m_appendChild);
557 m_appendChild->ref();
560 AppendNodeCommandImpl::~AppendNodeCommandImpl()
562 if (m_parentNode)
563 m_parentNode->deref();
564 if (m_appendChild)
565 m_appendChild->deref();
568 int AppendNodeCommandImpl::commandID() const
570 return AppendNodeCommandID;
573 void AppendNodeCommandImpl::doApply()
575 assert(m_parentNode);
576 assert(m_appendChild);
578 int exceptionCode = 0;
579 m_parentNode->appendChild(m_appendChild, exceptionCode);
580 assert(exceptionCode == 0);
583 void AppendNodeCommandImpl::doUnapply()
585 assert(m_parentNode);
586 assert(m_appendChild);
587 assert(state() == Applied);
589 int exceptionCode = 0;
590 m_parentNode->removeChild(m_appendChild, exceptionCode);
591 assert(exceptionCode == 0);
594 //------------------------------------------------------------------------------------------
595 // ApplyStyleCommandImpl
597 ApplyStyleCommandImpl::ApplyStyleCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *style)
598 : CompositeEditCommandImpl(document), m_style(style)
600 assert(m_style);
601 m_style->ref();
604 ApplyStyleCommandImpl::~ApplyStyleCommandImpl()
606 assert(m_style);
607 m_style->deref();
610 int ApplyStyleCommandImpl::commandID() const
612 return ApplyStyleCommandID;
615 void ApplyStyleCommandImpl::doApply()
617 if (endingSelection().state() != Selection::RANGE)
618 return;
620 // adjust to the positions we want to use for applying style
621 Position start(endingSelection().start().equivalentDownstreamPosition().equivalentRangeCompliantPosition());
622 Position end(endingSelection().end().equivalentUpstreamPosition());
624 // remove style from the selection
625 removeStyle(start, end);
626 bool splitStart = splitTextAtStartIfNeeded(start, end);
627 if (splitStart) {
628 start = endingSelection().start();
629 end = endingSelection().end();
631 splitTextAtEndIfNeeded(start, end);
632 start = endingSelection().start();
633 end = endingSelection().end();
636 if (start.node() == end.node()) {
637 // simple case...start and end are the same node
638 applyStyleIfNeeded(start.node(), end.node());
640 else {
641 NodeImpl *node = start.node();
642 while (1) {
643 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
644 NodeImpl *runStart = node;
645 while (1) {
646 if (runStart->parentNode() != node->parentNode() || node->isHTMLElement() || node == end.node() ||
647 (node->renderer() && !node->renderer()->isInline())) {
648 applyStyleIfNeeded(runStart, node);
649 break;
651 node = node->traverseNextNode();
654 if (node == end.node())
655 break;
656 node = node->traverseNextNode();
661 //------------------------------------------------------------------------------------------
662 // ApplyStyleCommandImpl: style-removal helpers
664 bool ApplyStyleCommandImpl::isHTMLStyleNode(HTMLElementImpl *elem)
666 QListIterator<CSSProperty*> it(*(style()->values()));
667 while (it.hasNext()) {
668 CSSProperty *property = it.next();
669 switch (property->id()) {
670 case CSS_PROP_FONT_WEIGHT:
671 if (elem->id() == ID_B)
672 return true;
673 break;
674 case CSS_PROP_FONT_STYLE:
675 if (elem->id() == ID_I)
676 return true;
677 break;
681 return false;
684 void ApplyStyleCommandImpl::removeHTMLStyleNode(HTMLElementImpl *elem)
686 // This node can be removed.
687 // EDIT FIXME: This does not handle the case where the node
688 // has attributes. But how often do people add attributes to <B> tags?
689 // Not so often I think.
690 assert(elem);
691 removeNodePreservingChildren(elem);
694 void ApplyStyleCommandImpl::removeCSSStyle(HTMLElementImpl *elem)
696 assert(elem);
698 CSSStyleDeclarationImpl *decl = elem->inlineStyleDecls();
699 if (!decl)
700 return;
702 QListIterator<CSSProperty*> it(*(style()->values()));
703 while ( it.hasNext() ) {
704 CSSProperty *property = it.next();
705 if (decl->getPropertyCSSValue(property->id()))
706 removeCSSProperty(decl, property->id());
709 if (elem->id() == ID_SPAN) {
710 // Check to see if the span is one we added to apply style.
711 // If it is, and there are no more attributes on the span other than our
712 // class marker, remove the span.
713 NamedAttrMapImpl *map = elem->attributes();
714 if (map && map->length() == 1 && elem->getAttribute(ATTR_CLASS) == styleSpanClassString())
715 removeNodePreservingChildren(elem);
719 void ApplyStyleCommandImpl::removeStyle(const Position &start, const Position &end)
721 NodeImpl *node = start.node();
722 while (1) {
723 NodeImpl *next = node->traverseNextNode();
724 if (node->isHTMLElement() && nodeFullySelected(node)) {
725 HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
726 if (isHTMLStyleNode(elem))
727 removeHTMLStyleNode(elem);
728 else
729 removeCSSStyle(elem);
731 if (node == end.node())
732 break;
733 node = next;
737 bool ApplyStyleCommandImpl::nodeFullySelected(const NodeImpl *node) const
739 assert(node);
741 Position end(endingSelection().end().equivalentUpstreamPosition());
743 if (node == end.node())
744 return end.offset() >= node->caretMaxOffset();
746 for (NodeImpl *child = node->lastChild(); child; child = child->lastChild()) {
747 if (child == end.node())
748 return end.offset() >= child->caretMaxOffset();
751 return node == end.node() || !node->isAncestor(end.node());
754 //------------------------------------------------------------------------------------------
755 // ApplyStyleCommandImpl: style-application helpers
758 bool ApplyStyleCommandImpl::splitTextAtStartIfNeeded(const Position &start, const Position &end)
760 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
761 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
762 TextImpl *text = static_cast<TextImpl *>(start.node());
763 SplitTextNodeCommand cmd(document(), text, start.offset());
764 applyCommandToComposite(cmd);
765 setEndingSelection(Selection(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment)));
766 return true;
768 return false;
771 NodeImpl *ApplyStyleCommandImpl::splitTextAtEndIfNeeded(const Position &start, const Position &end)
773 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
774 TextImpl *text = static_cast<TextImpl *>(end.node());
775 SplitTextNodeCommand cmd(document(), text, end.offset());
776 applyCommandToComposite(cmd);
777 NodeImpl *startNode = start.node() == end.node() ? cmd.node()->previousSibling() : start.node();
778 assert(startNode);
779 setEndingSelection(Selection(Position(startNode, start.offset()), Position(cmd.node()->previousSibling(), cmd.node()->previousSibling()->caretMaxOffset())));
780 return cmd.node()->previousSibling();
782 return end.node();
785 void ApplyStyleCommandImpl::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
787 assert(startNode);
788 assert(endNode);
789 assert(element);
791 NodeImpl *node = startNode;
792 while (1) {
793 NodeImpl *next = node->traverseNextNode();
794 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
795 removeNode(node);
796 appendNode(element, node);
798 if (node == endNode)
799 break;
800 node = next;
804 void ApplyStyleCommandImpl::applyStyleIfNeeded(DOM::NodeImpl *startNode, DOM::NodeImpl *endNode)
806 StyleChange styleChange = computeStyleChange(Position(startNode, 0), style());
807 int exceptionCode = 0;
809 if (styleChange.cssStyle.length() > 0) {
810 ElementImpl *styleElement = document()->createHTMLElement("SPAN");
811 assert(exceptionCode == 0);
812 styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle);
813 styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
814 insertNodeBefore(styleElement, startNode);
815 surroundNodeRangeWithElement(startNode, endNode, styleElement);
818 if (styleChange.applyBold) {
819 ElementImpl *boldElement = document()->createHTMLElement("B");
820 assert(exceptionCode == 0);
821 insertNodeBefore(boldElement, startNode);
822 surroundNodeRangeWithElement(startNode, endNode, boldElement);
825 if (styleChange.applyItalic) {
826 ElementImpl *italicElement = document()->createHTMLElement("I");
827 assert(exceptionCode == 0);
828 insertNodeBefore(italicElement, startNode);
829 surroundNodeRangeWithElement(startNode, endNode, italicElement);
833 bool ApplyStyleCommandImpl::currentlyHasStyle(const Position &pos, const CSSProperty *property) const
835 assert(pos.notEmpty());
836 CSSStyleDeclarationImpl *decl = document()->defaultView()->getComputedStyle(pos.element(), 0);
837 assert(decl);
838 CSSValueImpl *value = decl->getPropertyCSSValue(property->id());
839 return strcasecmp(value->cssText(), property->value()->cssText()) == 0;
842 ApplyStyleCommandImpl::StyleChange ApplyStyleCommandImpl::computeStyleChange(const Position &insertionPoint, CSSStyleDeclarationImpl *style)
844 assert(insertionPoint.notEmpty());
845 assert(style);
847 StyleChange styleChange;
849 QListIterator<CSSProperty*> it(*(style->values()));
850 while ( it.hasNext() ) {
851 CSSProperty *property = it.next();
852 if (!currentlyHasStyle(insertionPoint, property)) {
853 switch (property->id()) {
854 case CSS_PROP_FONT_WEIGHT:
855 if (strcasecmp(property->value()->cssText(), "bold") == 0)
856 styleChange.applyBold = true;
857 else
858 styleChange.cssStyle += property->cssText();
859 break;
860 case CSS_PROP_FONT_STYLE: {
861 DOMString cssText(property->value()->cssText());
862 if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0)
863 styleChange.applyItalic = true;
864 else
865 styleChange.cssStyle += property->cssText();
867 break;
868 default:
869 styleChange.cssStyle += property->cssText();
870 break;
874 return styleChange;
877 Position ApplyStyleCommandImpl::positionInsertionPoint(Position pos)
879 if (pos.node()->isTextNode() && (pos.offset() > 0 && pos.offset() < pos.node()->maxOffset())) {
880 SplitTextNodeCommand split(document(), static_cast<TextImpl *>(pos.node()), pos.offset());
881 split.apply();
882 pos = Position(split.node(), 0);
885 #if 0
886 // EDIT FIXME: If modified to work with the internals of applying style,
887 // this code can work to optimize cases where a style change is taking place on
888 // a boundary between nodes where one of the nodes has the desired style. In other
889 // words, it is possible for content to be merged into existing nodes rather than adding
890 // additional markup.
891 if (currentlyHasStyle(pos))
892 return pos;
894 // try next node
895 if (pos.offset() >= pos.node()->caretMaxOffset()) {
896 NodeImpl *nextNode = pos.node()->traverseNextNode();
897 if (nextNode) {
898 Position next = Position(nextNode, 0);
899 if (currentlyHasStyle(next))
900 return next;
904 // try previous node
905 if (pos.offset() <= pos.node()->caretMinOffset()) {
906 NodeImpl *prevNode = pos.node()->traversePreviousNode();
907 if (prevNode) {
908 Position prev = Position(prevNode, prevNode->maxOffset());
909 if (currentlyHasStyle(prev))
910 return prev;
913 #endif
915 return pos;
918 //------------------------------------------------------------------------------------------
919 // DeleteCollapsibleWhitespaceCommandImpl
921 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document)
922 : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_hasSelectionToCollapse(false)
926 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document, const Selection &selection)
927 : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_selectionToCollapse(selection), m_hasSelectionToCollapse(true)
931 DeleteCollapsibleWhitespaceCommandImpl::~DeleteCollapsibleWhitespaceCommandImpl()
935 int DeleteCollapsibleWhitespaceCommandImpl::commandID() const
937 return DeleteCollapsibleWhitespaceCommandID;
940 static bool shouldDeleteUpstreamPosition(const Position &pos)
942 if (!pos.node()->isTextNode())
943 return false;
945 RenderObject *renderer = pos.node()->renderer();
946 if (!renderer)
947 return true;
949 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
950 if (pos.offset() >= (long)textNode->length())
951 return false;
953 if (pos.isLastRenderedPositionInEditableBlock())
954 return false;
956 if (pos.isFirstRenderedPositionOnLine() || pos.isLastRenderedPositionOnLine())
957 return false;
959 RenderText *textRenderer = static_cast<RenderText *>(renderer);
960 for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
961 if (pos.offset() < box->m_start) {
962 return true;
964 if (pos.offset() >= box->m_start && pos.offset() < box->m_start + box->m_len)
965 return false;
968 return true;
971 Position DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace(const Position &pos)
973 Position upstream = pos.equivalentUpstreamPosition();
974 Position downstream = pos.equivalentDownstreamPosition();
976 bool del = shouldDeleteUpstreamPosition(upstream);
978 kDebug(6200) << "pos:" << DOM::getPrintableName(pos.node()->id()) << "["<< pos.node() << ":" << pos.offset() << "]";
979 if (upstream == downstream) {
980 kDebug(6200) << "same:" << DOM::getPrintableName(upstream.node()->id()) << "["<< upstream.node()<< ":" << upstream.offset()<< "]";
982 else {
983 kDebug(6200) << "upstream:" << ( del ? "DELETE" : "SKIP") << DOM::getPrintableName(upstream.node()->id())<< "["<< upstream.node() << ":" << upstream.offset()<< "]";
984 PositionIterator it(upstream);
985 for (it.next(); it.current() != downstream; it.next()) {
986 if (it.current().node()->isTextNode() && (long)static_cast<TextImpl *>(it.current().node())->length() == it.current().offset())
987 kDebug(6200) << " node: AT END"<< DOM::getPrintableName(it.current().node()->id())<< "["<< it.current().node()<< ":" << it.current().offset()<< "]";
988 else
989 kDebug(6200) << " node: DELETE"<< DOM::getPrintableName(it.current().node()->id())<< "["<< it.current().node()<< ":" << it.current().offset()<< "]";
991 kDebug(6200) << "downstream:" << DOM::getPrintableName(downstream.node()->id()) << "["<< downstream.node() << ":" << downstream.offset()<< "]";
994 if (upstream == downstream)
995 return upstream;
997 PositionIterator it(upstream);
998 Position deleteStart = upstream;
999 if (!del) {
1000 deleteStart = it.peekNext();
1001 if (deleteStart == downstream)
1002 return upstream;
1005 Position endingPosition = upstream;
1007 while (it.current() != downstream) {
1009 Position next = it.peekNext();
1010 if (next.node() != deleteStart.node()) {
1011 assert(deleteStart.node()->isTextNode());
1012 TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node());
1013 unsigned long count = it.current().offset() - deleteStart.offset();
1014 if (count == textNode->length()) {
1015 kDebug(6200) << " removeNodeAndPrune 1:" << textNode;
1016 if (textNode == endingPosition.node())
1017 endingPosition = Position(next.node(), next.node()->caretMinOffset());
1018 removeNodeAndPrune(textNode);
1020 else {
1021 kDebug(6200) << " deleteText 1:" << textNode << "t len:" << textNode->length()<<"start:" << deleteStart.offset() << "del len:" << (it.current().offset() - deleteStart.offset());
1022 deleteText(textNode, deleteStart.offset(), count);
1024 deleteStart = next;
1026 else if (next == downstream) {
1027 assert(deleteStart.node() == downstream.node());
1028 assert(downstream.node()->isTextNode());
1029 TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node());
1030 unsigned long count = downstream.offset() - deleteStart.offset();
1031 assert(count <= textNode->length());
1032 if (count == textNode->length()) {
1033 kDebug(6200) << " removeNodeAndPrune 2:"<<textNode;
1034 removeNodeAndPrune(textNode);
1036 else {
1037 kDebug(6200) << " deleteText 2:"<< textNode<< "t len:" << textNode->length() <<"start:" <<deleteStart.offset() << "del len:" << count;
1038 deleteText(textNode, deleteStart.offset(), count);
1039 m_charactersDeleted = count;
1040 endingPosition = Position(downstream.node(), downstream.offset() - m_charactersDeleted);
1044 it.setPosition(next);
1047 return endingPosition;
1050 void DeleteCollapsibleWhitespaceCommandImpl::doApply()
1052 // If selection has not been set to a custom selection when the command was created,
1053 // use the current ending selection.
1054 if (!m_hasSelectionToCollapse)
1055 m_selectionToCollapse = endingSelection();
1056 int state = m_selectionToCollapse.state();
1057 if (state == Selection::CARET) {
1058 Position endPosition = deleteWhitespace(m_selectionToCollapse.start());
1059 setEndingSelection(endPosition);
1060 kDebug(6200) << "-----------------------------------------------------";
1062 else if (state == Selection::RANGE) {
1063 Position startPosition = deleteWhitespace(m_selectionToCollapse.start());
1064 kDebug(6200) << "-----------------------------------------------------";
1065 Position endPosition = m_selectionToCollapse.end();
1066 if (m_charactersDeleted > 0 && startPosition.node() == endPosition.node()) {
1067 kDebug(6200) << "adjust end position by" << m_charactersDeleted;
1068 endPosition = Position(endPosition.node(), endPosition.offset() - m_charactersDeleted);
1070 endPosition = deleteWhitespace(endPosition);
1071 setEndingSelection(Selection(startPosition, endPosition));
1072 kDebug(6200) << "=====================================================";
1076 //------------------------------------------------------------------------------------------
1077 // DeleteSelectionCommandImpl
1079 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document)
1080 : CompositeEditCommandImpl(document), m_hasSelectionToDelete(false)
1084 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document, const Selection &selection)
1085 : CompositeEditCommandImpl(document), m_selectionToDelete(selection), m_hasSelectionToDelete(true)
1089 DeleteSelectionCommandImpl::~DeleteSelectionCommandImpl()
1093 int DeleteSelectionCommandImpl::commandID() const
1095 return DeleteSelectionCommandID;
1098 void DeleteSelectionCommandImpl::joinTextNodesWithSameStyle()
1100 Selection selection = endingSelection();
1102 if (selection.state() != Selection::CARET)
1103 return;
1105 Position pos(selection.start());
1107 if (!pos.node()->isTextNode())
1108 return;
1110 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1112 if (pos.offset() == 0) {
1113 PositionIterator it(pos);
1114 Position prev = it.previous();
1115 if (prev == pos)
1116 return;
1117 if (prev.node()->isTextNode()) {
1118 TextImpl *prevTextNode = static_cast<TextImpl *>(prev.node());
1119 if (textNodesAreJoinable(prevTextNode, textNode)) {
1120 joinTextNodes(prevTextNode, textNode);
1121 setEndingSelection(Position(textNode, prevTextNode->length()));
1122 kDebug(6200) << "joinTextNodesWithSameStyle [1]";
1126 else if (pos.offset() == (long)textNode->length()) {
1127 PositionIterator it(pos);
1128 Position next = it.next();
1129 if (next == pos)
1130 return;
1131 if (next.node()->isTextNode()) {
1132 TextImpl *nextTextNode = static_cast<TextImpl *>(next.node());
1133 if (textNodesAreJoinable(textNode, nextTextNode)) {
1134 joinTextNodes(textNode, nextTextNode);
1135 setEndingSelection(Position(nextTextNode, pos.offset()));
1136 kDebug(6200) << "joinTextNodesWithSameStyle [2]";
1142 bool DeleteSelectionCommandImpl::containsOnlyWhitespace(const Position &start, const Position &end)
1144 // Returns whether the range contains only whitespace characters.
1145 // This is inclusive of the start, but not of the end.
1146 PositionIterator it(start);
1147 while (!it.atEnd()) {
1148 if (!it.current().node()->isTextNode())
1149 return false;
1150 const DOMString &text = static_cast<TextImpl *>(it.current().node())->data();
1151 // EDIT FIXME: signed/unsigned mismatch
1152 if (text.length() > INT_MAX)
1153 return false;
1154 if (it.current().offset() < (int)text.length() && !isWS(text[it.current().offset()]))
1155 return false;
1156 it.next();
1157 if (it.current() == end)
1158 break;
1160 return true;
1163 void DeleteSelectionCommandImpl::doApply()
1165 // If selection has not been set to a custom selection when the command was created,
1166 // use the current ending selection.
1167 if (!m_hasSelectionToDelete)
1168 m_selectionToDelete = endingSelection();
1170 if (m_selectionToDelete.state() != Selection::RANGE)
1171 return;
1173 deleteCollapsibleWhitespace(m_selectionToDelete);
1174 Selection selection = endingSelection();
1176 Position upstreamStart(selection.start().equivalentUpstreamPosition());
1177 Position downstreamStart(selection.start().equivalentDownstreamPosition());
1178 Position upstreamEnd(selection.end().equivalentUpstreamPosition());
1179 Position downstreamEnd(selection.end().equivalentDownstreamPosition());
1181 if (upstreamStart == downstreamEnd)
1182 // after collapsing whitespace, selection is empty...no work to do
1183 return;
1185 Position endingPosition;
1186 bool adjustEndingPositionDownstream = false;
1188 bool onlyWhitespace = containsOnlyWhitespace(upstreamStart, downstreamEnd);
1190 bool startCompletelySelected = !onlyWhitespace &&
1191 (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset() &&
1192 ((downstreamStart.node() != upstreamEnd.node()) ||
1193 (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset())));
1195 bool endCompletelySelected = !onlyWhitespace &&
1196 (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset() &&
1197 ((downstreamStart.node() != upstreamEnd.node()) ||
1198 (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset())));
1200 unsigned long startRenderedOffset = downstreamStart.renderedOffset();
1202 bool startAtStartOfRootEditableElement = startRenderedOffset == 0 && downstreamStart.inFirstEditableInRootEditableElement();
1203 bool startAtStartOfBlock = startAtStartOfRootEditableElement ||
1204 (startRenderedOffset == 0 && downstreamStart.inFirstEditableInContainingEditableBlock());
1205 bool endAtEndOfBlock = downstreamEnd.isLastRenderedPositionInEditableBlock();
1207 NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement();
1208 NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement();
1209 bool startBlockEndBlockAreSiblings = startBlock->parentNode() == endBlock->parentNode();
1211 debugPosition("upstreamStart: ", upstreamStart);
1212 debugPosition("downstreamStart: ", downstreamStart);
1213 debugPosition("upstreamEnd: ", upstreamEnd);
1214 debugPosition("downstreamEnd: ", downstreamEnd);
1215 kDebug(6200) << "start selected:" << (startCompletelySelected ? "YES" : "NO");
1216 kDebug(6200) << "at start block:" << (startAtStartOfBlock ? "YES" : "NO");
1217 kDebug(6200) << "at start root block:"<< (startAtStartOfRootEditableElement ? "YES" : "NO");
1218 kDebug(6200) << "at end block:"<< (endAtEndOfBlock ? "YES" : "NO");
1219 kDebug(6200) << "only whitespace:"<< (onlyWhitespace ? "YES" : "NO");
1221 // Determine where to put the caret after the deletion
1222 if (startAtStartOfBlock) {
1223 kDebug(6200) << "ending position case 1";
1224 endingPosition = Position(startBlock, 0);
1225 adjustEndingPositionDownstream = true;
1227 else if (!startCompletelySelected) {
1228 kDebug(6200) << "ending position case 2";
1229 endingPosition = upstreamStart;
1230 if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1)
1231 adjustEndingPositionDownstream = true;
1233 else if (upstreamStart != downstreamStart) {
1234 kDebug(6200) << "ending position case 3";
1235 endingPosition = upstreamStart;
1236 if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1)
1237 adjustEndingPositionDownstream = true;
1241 // Figure out the whitespace conversions to do
1243 if ((startAtStartOfBlock && !endAtEndOfBlock) || (!startCompletelySelected && adjustEndingPositionDownstream)) {
1244 // convert trailing whitespace
1245 Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
1246 if (trailing.notEmpty()) {
1247 debugPosition("convertTrailingWhitespace: ", trailing);
1248 Position collapse = trailing.nextCharacterPosition();
1249 if (collapse != trailing)
1250 deleteCollapsibleWhitespace(collapse);
1251 TextImpl *textNode = static_cast<TextImpl *>(trailing.node());
1252 replaceText(textNode, trailing.offset(), 1, nonBreakingSpaceString());
1255 else if (!startAtStartOfBlock && endAtEndOfBlock) {
1256 // convert leading whitespace
1257 Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
1258 if (leading.notEmpty()) {
1259 debugPosition("convertLeadingWhitespace: ", leading);
1260 TextImpl *textNode = static_cast<TextImpl *>(leading.node());
1261 replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
1264 else if (!startAtStartOfBlock && !endAtEndOfBlock) {
1265 // convert contiguous whitespace
1266 Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
1267 Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
1268 if (leading.notEmpty() && trailing.notEmpty()) {
1269 debugPosition("convertLeadingWhitespace [contiguous]: ", leading);
1270 TextImpl *textNode = static_cast<TextImpl *>(leading.node());
1271 replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
1276 // Do the delete
1278 NodeImpl *n = downstreamStart.node()->traverseNextNode();
1280 // work on start node
1281 if (startCompletelySelected) {
1282 kDebug(6200) << "start node delete case 1";
1283 removeNodeAndPrune(downstreamStart.node(), startBlock);
1285 else if (onlyWhitespace) {
1286 // Selection only contains whitespace. This is really a special-case to
1287 // handle significant whitespace that is collapsed at the end of a line,
1288 // but also handles deleting a space in mid-line.
1289 kDebug(6200) << "start node delete case 2";
1290 assert(upstreamStart.node()->isTextNode());
1291 TextImpl *text = static_cast<TextImpl *>(upstreamStart.node());
1292 int offset = upstreamStart.offset();
1293 // EDIT FIXME: Signed/unsigned mismatch
1294 int length = text->length();
1295 if (length == upstreamStart.offset())
1296 offset--;
1297 deleteText(text, offset, 1);
1299 else if (downstreamStart.node()->isTextNode()) {
1300 kDebug(6200) << "start node delete case 3";
1301 TextImpl *text = static_cast<TextImpl *>(downstreamStart.node());
1302 int endOffset = text == upstreamEnd.node() ? upstreamEnd.offset() : text->length();
1303 if (endOffset > downstreamStart.offset()) {
1304 deleteText(text, downstreamStart.offset(), endOffset - downstreamStart.offset());
1307 else {
1308 // we have clipped the end of a non-text element
1309 // the offset must be 1 here. if it is, do nothing and move on.
1310 kDebug(6200) << "start node delete case 4";
1311 assert(downstreamStart.offset() == 1);
1314 if (!onlyWhitespace && downstreamStart.node() != upstreamEnd.node()) {
1315 // work on intermediate nodes
1316 while (n != upstreamEnd.node()) {
1317 NodeImpl *d = n;
1318 n = n->traverseNextNode();
1319 if (d->renderer() && d->renderer()->isEditable())
1320 removeNodeAndPrune(d, startBlock);
1323 // work on end node
1324 assert(n == upstreamEnd.node());
1325 if (endCompletelySelected) {
1326 removeNodeAndPrune(upstreamEnd.node(), startBlock);
1328 else if (upstreamEnd.node()->isTextNode()) {
1329 if (upstreamEnd.offset() > 0) {
1330 TextImpl *text = static_cast<TextImpl *>(upstreamEnd.node());
1331 deleteText(text, 0, upstreamEnd.offset());
1334 else {
1335 // we have clipped the beginning of a non-text element
1336 // the offset must be 0 here. if it is, do nothing and move on.
1337 assert(downstreamStart.offset() == 0);
1341 // Do block merge if start and end of selection are in different blocks
1342 // and the blocks are siblings. This is a first cut at this rule arrived
1343 // at by doing a bunch of edits and settling on the behavior that made
1344 // the most sense. This could change in the future as we get more
1345 // experience with how this should behave.
1346 if (startBlock != endBlock && startBlockEndBlockAreSiblings) {
1347 kDebug(6200) << "merging content to start block";
1348 NodeImpl *node = endBlock->firstChild();
1349 while (node) {
1350 NodeImpl *moveNode = node;
1351 node = node->nextSibling();
1352 removeNode(moveNode);
1353 appendNode(startBlock, moveNode);
1357 if (adjustEndingPositionDownstream) {
1358 kDebug(6200) << "adjust ending position downstream";
1359 endingPosition = endingPosition.equivalentDownstreamPosition();
1362 debugPosition("ending position: ", endingPosition);
1363 setEndingSelection(endingPosition);
1365 kDebug(6200) << "-----------------------------------------------------";
1368 //------------------------------------------------------------------------------------------
1369 // DeleteTextCommandImpl
1371 DeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, long count)
1372 : EditCommandImpl(document), m_node(node), m_offset(offset), m_count(count)
1374 assert(m_node);
1375 assert(m_offset >= 0);
1376 assert(m_count >= 0);
1378 m_node->ref();
1381 DeleteTextCommandImpl::~DeleteTextCommandImpl()
1383 if (m_node)
1384 m_node->deref();
1387 int DeleteTextCommandImpl::commandID() const
1389 return DeleteTextCommandID;
1392 void DeleteTextCommandImpl::doApply()
1394 assert(m_node);
1396 int exceptionCode = 0;
1397 m_text = m_node->substringData(m_offset, m_count, exceptionCode);
1398 assert(exceptionCode == 0);
1400 m_node->deleteData(m_offset, m_count, exceptionCode);
1401 assert(exceptionCode == 0);
1404 void DeleteTextCommandImpl::doUnapply()
1406 assert(m_node);
1407 assert(!m_text.isEmpty());
1409 int exceptionCode = 0;
1410 m_node->insertData(m_offset, m_text, exceptionCode);
1411 assert(exceptionCode == 0);
1414 //------------------------------------------------------------------------------------------
1415 // InputNewlineCommandImpl
1417 InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document)
1418 : CompositeEditCommandImpl(document)
1422 InputNewlineCommandImpl::~InputNewlineCommandImpl()
1426 int InputNewlineCommandImpl::commandID() const
1428 return InputNewlineCommandID;
1431 void InputNewlineCommandImpl::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
1433 // Insert the BR after the caret position. In the case the
1434 // position is a block, do an append. We don't want to insert
1435 // the BR *after* the block.
1436 Position upstream(pos.equivalentUpstreamPosition());
1437 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
1438 if (cb == pos.node())
1439 appendNode(cb, node);
1440 else
1441 insertNodeAfter(node, pos.node());
1444 void InputNewlineCommandImpl::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
1446 // Insert the BR after the caret position. In the case the
1447 // position is a block, do an append. We don't want to insert
1448 // the BR *before* the block.
1449 Position upstream(pos.equivalentUpstreamPosition());
1450 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
1451 if (cb == pos.node())
1452 appendNode(cb, node);
1453 else
1454 insertNodeBefore(node, pos.node());
1457 void InputNewlineCommandImpl::doApply()
1459 deleteSelection();
1460 Selection selection = endingSelection();
1462 int exceptionCode = 0;
1463 ElementImpl *breakNode = document()->createHTMLElement("BR");
1464 // assert(exceptionCode == 0);
1466 NodeImpl *nodeToInsert = breakNode;
1468 // Handle the case where there is a typing style.
1469 if (document()->part()->editor()->typingStyle()) {
1470 int exceptionCode = 0;
1471 ElementImpl *styleElement = createTypingStyleElement();
1472 styleElement->appendChild(breakNode, exceptionCode);
1473 assert(exceptionCode == 0);
1474 nodeToInsert = styleElement;
1477 Position pos(selection.start().equivalentDownstreamPosition());
1478 bool atStart = pos.offset() <= pos.node()->caretMinOffset();
1479 bool atEndOfBlock = pos.isLastRenderedPositionInEditableBlock();
1481 if (atEndOfBlock) {
1482 kDebug(6200) << "input newline case 1";
1483 // Insert an "extra" BR at the end of the block. This makes the "real" BR we want
1484 // to insert appear in the rendering without any significant side effects (and no
1485 // real worries either since you can't arrow past this extra one.
1486 insertNodeAfterPosition(nodeToInsert, pos);
1487 exceptionCode = 0;
1488 ElementImpl *extraBreakNode = document()->createHTMLElement("BR");
1489 // assert(exceptionCode == 0);
1490 insertNodeAfter(extraBreakNode, nodeToInsert);
1491 setEndingSelection(Position(extraBreakNode, 0));
1493 else if (atStart) {
1494 kDebug(6200) << "input newline case 2";
1495 // Insert node, but place the caret into index 0 of the downstream
1496 // position. This will make the caret appear after the break, and as we know
1497 // there is content at that location, this is OK.
1498 insertNodeBeforePosition(nodeToInsert, pos);
1499 setEndingSelection(Position(pos.node(), 0));
1501 else {
1502 // Split a text node
1503 kDebug(6200) << "input newline case 3";
1504 assert(pos.node()->isTextNode());
1505 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1506 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
1507 deleteText(textNode, 0, selection.start().offset());
1508 insertNodeBefore(textBeforeNode, textNode);
1509 insertNodeBefore(nodeToInsert, textNode);
1510 setEndingSelection(Position(textNode, 0));
1514 //------------------------------------------------------------------------------------------
1515 // InputTextCommandImpl
1517 InputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document)
1518 : CompositeEditCommandImpl(document), m_charactersAdded(0)
1522 InputTextCommandImpl::~InputTextCommandImpl()
1526 int InputTextCommandImpl::commandID() const
1528 return InputTextCommandID;
1531 void InputTextCommandImpl::doApply()
1535 void InputTextCommandImpl::input(const DOMString &text)
1537 execute(text);
1540 void InputTextCommandImpl::deleteCharacter()
1542 assert(state() == Applied);
1544 Selection selection = endingSelection();
1546 if (!selection.start().node()->isTextNode())
1547 return;
1549 int exceptionCode = 0;
1550 int offset = selection.start().offset() - 1;
1551 if (offset >= selection.start().node()->caretMinOffset()) {
1552 TextImpl *textNode = static_cast<TextImpl *>(selection.start().node());
1553 textNode->deleteData(offset, 1, exceptionCode);
1554 assert(exceptionCode == 0);
1555 selection = Selection(Position(textNode, offset));
1556 setEndingSelection(selection);
1557 m_charactersAdded--;
1561 Position InputTextCommandImpl::prepareForTextInsertion(bool adjustDownstream)
1563 // Prepare for text input by looking at the current position.
1564 // It may be necessary to insert a text node to receive characters.
1565 Selection selection = endingSelection();
1566 assert(selection.state() == Selection::CARET);
1568 Position pos = selection.start();
1569 if (adjustDownstream)
1570 pos = pos.equivalentDownstreamPosition();
1571 else
1572 pos = pos.equivalentUpstreamPosition();
1574 if (!pos.node()->isTextNode()) {
1575 NodeImpl *textNode = document()->createEditingTextNode("");
1576 NodeImpl *nodeToInsert = textNode;
1577 if (document()->part()->editor()->typingStyle()) {
1578 int exceptionCode = 0;
1579 ElementImpl *styleElement = createTypingStyleElement();
1580 styleElement->appendChild(textNode, exceptionCode);
1581 assert(exceptionCode == 0);
1582 nodeToInsert = styleElement;
1585 // Now insert the node in the right place
1586 if (pos.node()->isEditableBlock()) {
1587 kDebug(6200) << "prepareForTextInsertion case 1";
1588 appendNode(pos.node(), nodeToInsert);
1590 else if (pos.node()->id() == ID_BR && pos.offset() == 1) {
1591 kDebug(6200) << "prepareForTextInsertion case 2";
1592 insertNodeAfter(nodeToInsert, pos.node());
1594 else if (pos.node()->caretMinOffset() == pos.offset()) {
1595 kDebug(6200) << "prepareForTextInsertion case 3";
1596 insertNodeBefore(nodeToInsert, pos.node());
1598 else if (pos.node()->caretMaxOffset() == pos.offset()) {
1599 kDebug(6200) << "prepareForTextInsertion case 4";
1600 insertNodeAfter(nodeToInsert, pos.node());
1602 else
1603 assert(false);
1605 pos = Position(textNode, 0);
1607 else {
1608 // Handle the case where there is a typing style.
1609 if (document()->part()->editor()->typingStyle()) {
1610 if (pos.node()->isTextNode() && pos.offset() > pos.node()->caretMinOffset() && pos.offset() < pos.node()->caretMaxOffset()) {
1611 // Need to split current text node in order to insert a span.
1612 TextImpl *text = static_cast<TextImpl *>(pos.node());
1613 SplitTextNodeCommand cmd(document(), text, pos.offset());
1614 applyCommandToComposite(cmd);
1615 setEndingSelection(Position(cmd.node(), 0));
1618 int exceptionCode = 0;
1619 TextImpl *editingTextNode = document()->createEditingTextNode("");
1621 ElementImpl *styleElement = createTypingStyleElement();
1622 styleElement->appendChild(editingTextNode, exceptionCode);
1623 assert(exceptionCode == 0);
1625 NodeImpl *node = endingSelection().start().node();
1626 if (endingSelection().start().isLastRenderedPositionOnLine())
1627 insertNodeAfter(styleElement, node);
1628 else
1629 insertNodeBefore(styleElement, node);
1630 pos = Position(editingTextNode, 0);
1633 return pos;
1636 void InputTextCommandImpl::execute(const DOMString &text)
1638 Selection selection = endingSelection();
1639 bool adjustDownstream = selection.start().isFirstRenderedPositionOnLine();
1641 // Delete the current selection, or collapse whitespace, as needed
1642 if (selection.state() == Selection::RANGE)
1643 deleteSelection();
1644 else
1645 deleteCollapsibleWhitespace();
1647 // EDIT FIXME: Need to take typing style from upstream text, if any.
1649 // Make sure the document is set up to receive text
1650 Position pos = prepareForTextInsertion(adjustDownstream);
1652 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1653 long offset = pos.offset();
1655 // This is a temporary implementation for inserting adjoining spaces
1656 // into a document. We are working on a CSS-related whitespace solution
1657 // that will replace this some day.
1658 if (isWS(text))
1659 insertSpace(textNode, offset);
1660 else {
1661 const DOMString &existingText = textNode->data();
1662 if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isWS(existingText[offset - 2])) {
1663 // DOM looks like this:
1664 // character nbsp caret
1665 // As we are about to insert a non-whitespace character at the caret
1666 // convert the nbsp to a regular space.
1667 // EDIT FIXME: This needs to be improved some day to convert back only
1668 // those nbsp's added by the editor to make rendering come out right.
1669 replaceText(textNode, offset - 1, 1, " ");
1671 insertText(textNode, offset, text);
1673 setEndingSelection(Position(textNode, offset + text.length()));
1674 m_charactersAdded += text.length();
1677 void InputTextCommandImpl::insertSpace(TextImpl *textNode, unsigned long offset)
1679 assert(textNode);
1681 DOMString text(textNode->data());
1683 // count up all spaces and newlines in front of the caret
1684 // delete all collapsed ones
1685 // this will work out OK since the offset we have been passed has been upstream-ized
1686 int count = 0;
1687 for (unsigned int i = offset; i < text.length(); i++) {
1688 if (isWS(text[i]))
1689 count++;
1690 else
1691 break;
1693 if (count > 0) {
1694 // By checking the character at the downstream position, we can
1695 // check if there is a rendered WS at the caret
1696 Position pos(textNode, offset);
1697 Position downstream = pos.equivalentDownstreamPosition();
1698 if (downstream.offset() < (long)text.length() && isWS(text[downstream.offset()]))
1699 count--; // leave this WS in
1700 if (count > 0)
1701 deleteText(textNode, offset, count);
1704 if (offset > 0 && offset <= text.length() - 1 && !isWS(text[offset]) && !isWS(text[offset - 1])) {
1705 // insert a "regular" space
1706 insertText(textNode, offset, " ");
1707 return;
1710 if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
1711 // DOM looks like this:
1712 // nbsp nbsp caret
1713 // insert a space between the two nbsps
1714 insertText(textNode, offset - 1, " ");
1715 return;
1718 // insert an nbsp
1719 insertText(textNode, offset, nonBreakingSpaceString());
1722 //------------------------------------------------------------------------------------------
1723 // InsertNodeBeforeCommandImpl
1725 InsertNodeBeforeCommandImpl::InsertNodeBeforeCommandImpl(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
1726 : EditCommandImpl(document), m_insertChild(insertChild), m_refChild(refChild)
1728 assert(m_insertChild);
1729 m_insertChild->ref();
1731 assert(m_refChild);
1732 m_refChild->ref();
1735 InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl()
1737 if (m_insertChild)
1738 m_insertChild->deref();
1739 if (m_refChild)
1740 m_refChild->deref();
1743 int InsertNodeBeforeCommandImpl::commandID() const
1745 return InsertNodeBeforeCommandID;
1748 void InsertNodeBeforeCommandImpl::doApply()
1750 assert(m_insertChild);
1751 assert(m_refChild);
1752 assert(m_refChild->parentNode());
1754 int exceptionCode = 0;
1755 m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
1756 assert(exceptionCode == 0);
1759 void InsertNodeBeforeCommandImpl::doUnapply()
1761 assert(m_insertChild);
1762 assert(m_refChild);
1763 assert(m_refChild->parentNode());
1765 int exceptionCode = 0;
1766 m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
1767 assert(exceptionCode == 0);
1770 //------------------------------------------------------------------------------------------
1771 // InsertTextCommandImpl
1773 InsertTextCommandImpl::InsertTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
1774 : EditCommandImpl(document), m_node(node), m_offset(offset)
1776 assert(m_node);
1777 assert(m_offset >= 0);
1778 assert(text.length() > 0);
1780 m_node->ref();
1781 m_text = text.copy(); // make a copy to ensure that the string never changes
1784 InsertTextCommandImpl::~InsertTextCommandImpl()
1786 if (m_node)
1787 m_node->deref();
1790 int InsertTextCommandImpl::commandID() const
1792 return InsertTextCommandID;
1795 void InsertTextCommandImpl::doApply()
1797 assert(m_node);
1798 assert(!m_text.isEmpty());
1800 int exceptionCode = 0;
1801 m_node->insertData(m_offset, m_text, exceptionCode);
1802 assert(exceptionCode == 0);
1805 void InsertTextCommandImpl::doUnapply()
1807 assert(m_node);
1808 assert(!m_text.isEmpty());
1810 int exceptionCode = 0;
1811 m_node->deleteData(m_offset, m_text.length(), exceptionCode);
1812 assert(exceptionCode == 0);
1815 //------------------------------------------------------------------------------------------
1816 // JoinTextNodesCommandImpl
1818 JoinTextNodesCommandImpl::JoinTextNodesCommandImpl(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
1819 : EditCommandImpl(document), m_text1(text1), m_text2(text2)
1821 assert(m_text1);
1822 assert(m_text2);
1823 assert(m_text1->nextSibling() == m_text2);
1824 assert(m_text1->length() > 0);
1825 assert(m_text2->length() > 0);
1827 m_text1->ref();
1828 m_text2->ref();
1831 JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl()
1833 if (m_text1)
1834 m_text1->deref();
1835 if (m_text2)
1836 m_text2->deref();
1839 int JoinTextNodesCommandImpl::commandID() const
1841 return JoinTextNodesCommandID;
1844 void JoinTextNodesCommandImpl::doApply()
1846 assert(m_text1);
1847 assert(m_text2);
1848 assert(m_text1->nextSibling() == m_text2);
1850 int exceptionCode = 0;
1851 m_text2->insertData(0, m_text1->data(), exceptionCode);
1852 assert(exceptionCode == 0);
1854 m_text2->parentNode()->removeChild(m_text1, exceptionCode);
1855 assert(exceptionCode == 0);
1857 m_offset = m_text1->length();
1860 void JoinTextNodesCommandImpl::doUnapply()
1862 assert(m_text2);
1863 assert(m_offset > 0);
1865 int exceptionCode = 0;
1867 m_text2->deleteData(0, m_offset, exceptionCode);
1868 assert(exceptionCode == 0);
1870 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
1871 assert(exceptionCode == 0);
1873 assert(m_text2->previousSibling()->isTextNode());
1874 assert(m_text2->previousSibling() == m_text1);
1877 //------------------------------------------------------------------------------------------
1878 // ReplaceSelectionCommandImpl
1880 ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement)
1881 : CompositeEditCommandImpl(document), m_fragment(fragment), m_selectReplacement(selectReplacement)
1885 ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl()
1889 int ReplaceSelectionCommandImpl::commandID() const
1891 return ReplaceSelectionCommandID;
1894 void ReplaceSelectionCommandImpl::doApply()
1896 NodeImpl *firstChild = m_fragment->firstChild();
1897 NodeImpl *lastChild = m_fragment->lastChild();
1899 Selection selection = endingSelection();
1901 // Delete the current selection, or collapse whitespace, as needed
1902 if (selection.state() == Selection::RANGE)
1903 deleteSelection();
1904 else
1905 deleteCollapsibleWhitespace();
1907 selection = endingSelection();
1908 assert(!selection.isEmpty());
1910 if (!firstChild) {
1911 // Pasting something that didn't parse or was empty.
1912 assert(!lastChild);
1913 } else if (firstChild == lastChild && firstChild->isTextNode()) {
1914 // Simple text paste. Treat as if the text were typed.
1915 Position base = selection.base();
1916 inputText(static_cast<TextImpl *>(firstChild)->data());
1917 if (m_selectReplacement) {
1918 setEndingSelection(Selection(base, endingSelection().extent()));
1921 else {
1922 // HTML fragment paste.
1923 NodeImpl *beforeNode = firstChild;
1924 NodeImpl *node = firstChild->nextSibling();
1926 insertNodeAt(firstChild, selection.start().node(), selection.start().offset());
1928 // Insert the nodes from the fragment
1929 while (node) {
1930 NodeImpl *next = node->nextSibling();
1931 insertNodeAfter(node, beforeNode);
1932 beforeNode = node;
1933 node = next;
1935 assert(beforeNode);
1937 // Find the last leaf.
1938 NodeImpl *lastLeaf = lastChild;
1939 while (1) {
1940 NodeImpl *nextChild = lastLeaf->lastChild();
1941 if (!nextChild)
1942 break;
1943 lastLeaf = nextChild;
1946 if (m_selectReplacement) {
1947 // Find the first leaf.
1948 NodeImpl *firstLeaf = firstChild;
1949 while (1) {
1950 NodeImpl *nextChild = firstLeaf->firstChild();
1951 if (!nextChild)
1952 break;
1953 firstLeaf = nextChild;
1955 // Select what was inserted.
1956 setEndingSelection(Selection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset())));
1957 } else {
1958 // Place the cursor after what was inserted.
1959 setEndingSelection(Position(lastLeaf, lastLeaf->caretMaxOffset()));
1964 //------------------------------------------------------------------------------------------
1965 // MoveSelectionCommandImpl
1967 MoveSelectionCommandImpl::MoveSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position)
1968 : CompositeEditCommandImpl(document), m_fragment(fragment), m_position(position)
1972 MoveSelectionCommandImpl::~MoveSelectionCommandImpl()
1976 int MoveSelectionCommandImpl::commandID() const
1978 return MoveSelectionCommandID;
1981 void MoveSelectionCommandImpl::doApply()
1983 Selection selection = endingSelection();
1984 assert(selection.state() == Selection::RANGE);
1986 // Update the position otherwise it may become invalid after the selection is deleted.
1987 NodeImpl *positionNode = m_position.node();
1988 long positionOffset = m_position.offset();
1989 Position selectionEnd = selection.end();
1990 long selectionEndOffset = selectionEnd.offset();
1991 if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) {
1992 positionOffset -= selectionEndOffset;
1993 Position selectionStart = selection.start();
1994 if (selectionStart.node() == positionNode) {
1995 positionOffset += selectionStart.offset();
1999 deleteSelection();
2001 setEndingSelection(Position(positionNode, positionOffset));
2002 ReplaceSelectionCommand cmd(document(), m_fragment, true);
2003 applyCommandToComposite(cmd);
2006 //------------------------------------------------------------------------------------------
2007 // RemoveCSSPropertyCommandImpl
2009 RemoveCSSPropertyCommandImpl::RemoveCSSPropertyCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property)
2010 : EditCommandImpl(document), m_decl(decl), m_property(property), m_important(false)
2012 assert(m_decl);
2013 m_decl->ref();
2016 RemoveCSSPropertyCommandImpl::~RemoveCSSPropertyCommandImpl()
2018 assert(m_decl);
2019 m_decl->deref();
2022 int RemoveCSSPropertyCommandImpl::commandID() const
2024 return RemoveCSSPropertyCommandID;
2027 void RemoveCSSPropertyCommandImpl::doApply()
2029 assert(m_decl);
2031 m_oldValue = m_decl->getPropertyValue(m_property);
2032 assert(!m_oldValue.isNull());
2034 m_important = m_decl->getPropertyPriority(m_property);
2035 m_decl->removeProperty(m_property);
2038 void RemoveCSSPropertyCommandImpl::doUnapply()
2040 assert(m_decl);
2041 assert(!m_oldValue.isNull());
2043 m_decl->setProperty(m_property, m_oldValue, m_important);
2046 //------------------------------------------------------------------------------------------
2047 // RemoveNodeAttributeCommandImpl
2049 RemoveNodeAttributeCommandImpl::RemoveNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute)
2050 : EditCommandImpl(document), m_element(element), m_attribute(attribute)
2052 assert(m_element);
2053 m_element->ref();
2056 RemoveNodeAttributeCommandImpl::~RemoveNodeAttributeCommandImpl()
2058 assert(m_element);
2059 m_element->deref();
2062 int RemoveNodeAttributeCommandImpl::commandID() const
2064 return RemoveNodeAttributeCommandID;
2067 void RemoveNodeAttributeCommandImpl::doApply()
2069 assert(m_element);
2071 m_oldValue = m_element->getAttribute(m_attribute);
2072 assert(!m_oldValue.isNull());
2074 int exceptionCode = 0;
2075 m_element->removeAttribute(m_attribute, exceptionCode);
2076 assert(exceptionCode == 0);
2079 void RemoveNodeAttributeCommandImpl::doUnapply()
2081 assert(m_element);
2082 assert(!m_oldValue.isNull());
2084 // int exceptionCode = 0;
2085 m_element->setAttribute(m_attribute, m_oldValue.implementation());
2086 // assert(exceptionCode == 0);
2089 //------------------------------------------------------------------------------------------
2090 // RemoveNodeCommandImpl
2092 RemoveNodeCommandImpl::RemoveNodeCommandImpl(DocumentImpl *document, NodeImpl *removeChild)
2093 : EditCommandImpl(document), m_parent(0), m_removeChild(removeChild), m_refChild(0)
2095 assert(m_removeChild);
2096 m_removeChild->ref();
2098 m_parent = m_removeChild->parentNode();
2099 assert(m_parent);
2100 m_parent->ref();
2102 NodeListImpl *children = m_parent->childNodes();
2103 for (int i = children->length(); i >= 0; i--) {
2104 NodeImpl *node = children->item(i);
2105 if (node == m_removeChild)
2106 break;
2107 m_refChild = node;
2110 if (m_refChild)
2111 m_refChild->ref();
2114 RemoveNodeCommandImpl::~RemoveNodeCommandImpl()
2116 if (m_parent)
2117 m_parent->deref();
2118 if (m_removeChild)
2119 m_removeChild->deref();
2120 if (m_refChild)
2121 m_refChild->deref();
2124 int RemoveNodeCommandImpl::commandID() const
2126 return RemoveNodeCommandID;
2129 void RemoveNodeCommandImpl::doApply()
2131 assert(m_parent);
2132 assert(m_removeChild);
2134 int exceptionCode = 0;
2135 m_parent->removeChild(m_removeChild, exceptionCode);
2136 assert(exceptionCode == 0);
2139 void RemoveNodeCommandImpl::doUnapply()
2141 assert(m_parent);
2142 assert(m_removeChild);
2144 int exceptionCode = 0;
2145 if (m_refChild)
2146 m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
2147 else
2148 m_parent->appendChild(m_removeChild, exceptionCode);
2149 assert(exceptionCode == 0);
2152 //------------------------------------------------------------------------------------------
2153 // RemoveNodeAndPruneCommandImpl
2155 RemoveNodeAndPruneCommandImpl::RemoveNodeAndPruneCommandImpl(DocumentImpl *document, NodeImpl *pruneNode, NodeImpl *stopNode)
2156 : CompositeEditCommandImpl(document), m_pruneNode(pruneNode), m_stopNode(stopNode)
2158 assert(m_pruneNode);
2159 m_pruneNode->ref();
2160 if (m_stopNode)
2161 m_stopNode->ref();
2164 RemoveNodeAndPruneCommandImpl::~RemoveNodeAndPruneCommandImpl()
2166 m_pruneNode->deref();
2167 if (m_stopNode)
2168 m_stopNode->deref();
2171 int RemoveNodeAndPruneCommandImpl::commandID() const
2173 return RemoveNodeAndPruneCommandID;
2176 void RemoveNodeAndPruneCommandImpl::doApply()
2178 NodeImpl *editableBlock = m_pruneNode->enclosingBlockFlowElement();
2179 NodeImpl *pruneNode = m_pruneNode;
2180 NodeImpl *node = pruneNode->traversePreviousNode();
2181 removeNode(pruneNode);
2182 while (1) {
2183 if (node == m_stopNode || editableBlock != node->enclosingBlockFlowElement() || !shouldPruneNode(node))
2184 break;
2185 pruneNode = node;
2186 node = node->traversePreviousNode();
2187 removeNode(pruneNode);
2191 //------------------------------------------------------------------------------------------
2192 // RemoveNodePreservingChildrenCommandImpl
2194 RemoveNodePreservingChildrenCommandImpl::RemoveNodePreservingChildrenCommandImpl(DocumentImpl *document, NodeImpl *node)
2195 : CompositeEditCommandImpl(document), m_node(node)
2197 assert(m_node);
2198 m_node->ref();
2201 RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl()
2203 if (m_node)
2204 m_node->deref();
2207 int RemoveNodePreservingChildrenCommandImpl::commandID() const
2209 return RemoveNodePreservingChildrenCommandID;
2212 void RemoveNodePreservingChildrenCommandImpl::doApply()
2214 NodeListImpl *children = node()->childNodes();
2215 int length = children->length();
2216 for (int i = 0; i < length; i++) {
2217 NodeImpl *child = children->item(0);
2218 removeNode(child);
2219 insertNodeBefore(child, node());
2221 removeNode(node());
2224 //------------------------------------------------------------------------------------------
2225 // SetNodeAttributeCommandImpl
2227 SetNodeAttributeCommandImpl::SetNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value)
2228 : EditCommandImpl(document), m_element(element), m_attribute(attribute), m_value(value)
2230 assert(m_element);
2231 m_element->ref();
2232 assert(!m_value.isNull());
2235 SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl()
2237 if (m_element)
2238 m_element->deref();
2241 int SetNodeAttributeCommandImpl::commandID() const
2243 return SetNodeAttributeCommandID;
2246 void SetNodeAttributeCommandImpl::doApply()
2248 assert(m_element);
2249 assert(!m_value.isNull());
2251 // int exceptionCode = 0;
2252 m_oldValue = m_element->getAttribute(m_attribute);
2253 m_element->setAttribute(m_attribute, m_value.implementation());
2254 // assert(exceptionCode == 0);
2257 void SetNodeAttributeCommandImpl::doUnapply()
2259 assert(m_element);
2260 assert(!m_oldValue.isNull());
2262 // int exceptionCode = 0;
2263 m_element->setAttribute(m_attribute, m_oldValue.implementation());
2264 // assert(exceptionCode == 0);
2267 //------------------------------------------------------------------------------------------
2268 // SplitTextNodeCommandImpl
2270 SplitTextNodeCommandImpl::SplitTextNodeCommandImpl(DocumentImpl *document, TextImpl *text, long offset)
2271 : EditCommandImpl(document), m_text1(0), m_text2(text), m_offset(offset)
2273 assert(m_text2);
2274 assert(m_text2->length() > 0);
2276 m_text2->ref();
2279 SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl()
2281 if (m_text1)
2282 m_text1->deref();
2283 if (m_text2)
2284 m_text2->deref();
2287 int SplitTextNodeCommandImpl::commandID() const
2289 return SplitTextNodeCommandID;
2292 void SplitTextNodeCommandImpl::doApply()
2294 assert(m_text2);
2295 assert(m_offset > 0);
2297 int exceptionCode = 0;
2299 // EDIT FIXME: This should use better smarts for figuring out which portion
2300 // of the split to copy (based on their comparative sizes). We should also
2301 // just use the DOM's splitText function.
2303 if (!m_text1) {
2304 // create only if needed.
2305 // if reapplying, this object will already exist.
2306 m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode));
2307 assert(exceptionCode == 0);
2308 assert(m_text1);
2309 m_text1->ref();
2312 m_text2->deleteData(0, m_offset, exceptionCode);
2313 assert(exceptionCode == 0);
2315 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
2316 assert(exceptionCode == 0);
2318 assert(m_text2->previousSibling()->isTextNode());
2319 assert(m_text2->previousSibling() == m_text1);
2322 void SplitTextNodeCommandImpl::doUnapply()
2324 assert(m_text1);
2325 assert(m_text2);
2327 assert(m_text1->nextSibling() == m_text2);
2329 int exceptionCode = 0;
2330 m_text2->insertData(0, m_text1->data(), exceptionCode);
2331 assert(exceptionCode == 0);
2333 m_text2->parentNode()->removeChild(m_text1, exceptionCode);
2334 assert(exceptionCode == 0);
2336 m_offset = m_text1->length();
2339 //------------------------------------------------------------------------------------------
2340 // TypingCommandImpl
2342 TypingCommandImpl::TypingCommandImpl(DocumentImpl *document)
2343 : CompositeEditCommandImpl(document), m_openForMoreTyping(true)
2347 TypingCommandImpl::~TypingCommandImpl()
2351 int TypingCommandImpl::commandID() const
2353 return TypingCommandID;
2356 void TypingCommandImpl::doApply()
2360 void TypingCommandImpl::typingAddedToOpenCommand()
2362 assert(document());
2363 assert(document()->part());
2364 EditCommand cmd(this);
2365 document()->part()->editor()->appliedEditing(cmd);
2368 void TypingCommandImpl::insertText(const DOMString &text)
2370 if (document()->part()->editor()->typingStyle() || m_cmds.count() == 0) {
2371 InputTextCommand cmd(document());
2372 applyCommandToComposite(cmd);
2373 cmd.input(text);
2375 else {
2376 EditCommand lastCommand = m_cmds.last();
2377 if (lastCommand.commandID() == InputTextCommandID) {
2378 static_cast<InputTextCommand &>(lastCommand).input(text);
2380 else {
2381 InputTextCommand cmd(document());
2382 applyCommandToComposite(cmd);
2383 cmd.input(text);
2386 typingAddedToOpenCommand();
2389 void TypingCommandImpl::insertNewline()
2391 InputNewlineCommand cmd(document());
2392 applyCommandToComposite(cmd);
2393 typingAddedToOpenCommand();
2396 void TypingCommandImpl::issueCommandForDeleteKey()
2398 Selection selectionToDelete = endingSelection();
2399 assert(selectionToDelete.state() != Selection::NONE);
2401 if (selectionToDelete.state() == Selection::CARET) {
2402 Position pos(selectionToDelete.start());
2403 if (pos.inFirstEditableInRootEditableElement() && pos.offset() <= pos.node()->caretMinOffset()) {
2404 // we're at the start of a root editable block...do nothing
2405 return;
2407 selectionToDelete = Selection(pos.previousCharacterPosition(), pos);
2409 deleteSelection(selectionToDelete);
2410 typingAddedToOpenCommand();
2413 void TypingCommandImpl::deleteKeyPressed()
2415 // EDIT FIXME: The ifdef'ed out code below should be re-enabled.
2416 // In order for this to happen, the deleteCharacter case
2417 // needs work. Specifically, the caret-positioning code
2418 // and whitespace-handling code in DeleteSelectionCommandImpl::doApply()
2419 // needs to be factored out so it can be used again here.
2420 // Until that work is done, issueCommandForDeleteKey() does the
2421 // right thing, but less efficiently and with the cost of more
2422 // objects.
2423 issueCommandForDeleteKey();
2424 #if 0
2425 if (m_cmds.count() == 0) {
2426 issueCommandForDeleteKey();
2428 else {
2429 EditCommand lastCommand = m_cmds.last();
2430 if (lastCommand.commandID() == InputTextCommandID) {
2431 InputTextCommand cmd = static_cast<InputTextCommand &>(lastCommand);
2432 cmd.deleteCharacter();
2433 if (cmd.charactersAdded() == 0) {
2434 removeCommand(cmd);
2437 else if (lastCommand.commandID() == InputNewlineCommandID) {
2438 lastCommand.unapply();
2439 removeCommand(lastCommand);
2441 else {
2442 issueCommandForDeleteKey();
2445 #endif
2448 void TypingCommandImpl::removeCommand(const EditCommand &cmd)
2450 // NOTE: If the passed-in command is the last command in the
2451 // composite, we could remove all traces of this typing command
2452 // from the system, including the undo chain. Other editors do
2453 // not do this, but we could.
2455 m_cmds.removeAll(cmd);
2456 if (m_cmds.count() == 0)
2457 setEndingSelection(startingSelection());
2458 else
2459 setEndingSelection(m_cmds.last().endingSelection());
2462 //------------------------------------------------------------------------------------------
2464 } // namespace khtml