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
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"
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"
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
;
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
;
74 using DOM::NodeListImpl
;
80 using DOM::TreeWalkerImpl
;
83 #define debugPosition(a,b) ((void)0)
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)
104 return isWS(text
[0]);
107 static inline bool isWS(const Position
&pos
)
112 if (!pos
.node()->isTextNode())
115 const DOMString
&string
= static_cast<TextImpl
*>(pos
.node())->data();
116 return isWS(string
[pos
.offset()]);
119 static bool shouldPruneNode(NodeImpl
*node
)
124 RenderObject
*renderer
= node
->renderer();
128 if (node
->hasChildNodes())
131 if (node
->rootEditableElement() == node
)
134 if (renderer
->isBR() || renderer
->isReplaced())
137 if (node
->isTextNode()) {
138 TextImpl
*text
= static_cast<TextImpl
*>(node
);
139 if (text
->length() == 0)
144 if (!node
->isHTMLElement()/* && !node->isXMLElementNode()*/)
147 if (node
->id() == ID_BODY
)
150 if (!node
->isContentEditable())
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()]))
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();
186 DOMString string
= static_cast<TextImpl
*>(pos
.node())->data();
187 if (isWS(string
[pos
.offset()]))
195 static bool textNodesAreJoinable(TextImpl
*text1
, TextImpl
*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
;
216 static void debugPosition(const char *prefix
, const Position
&pos
)
218 kDebug(6200) << prefix
<< DOM::getPrintableName(pos
.node()->id()) << pos
.node() << pos
.offset();
222 //------------------------------------------------------------------------------------------
225 EditCommandImpl::EditCommandImpl(DocumentImpl
*document
)
226 : SharedCommandImpl(), m_document(document
), m_state(NotApplied
), m_parent(0)
229 assert(m_document
->part());
231 m_startingSelection
= m_document
->part()->caret();
232 m_endingSelection
= m_startingSelection
;
235 EditCommandImpl::~EditCommandImpl()
240 int EditCommandImpl::commandID() const
242 return EditCommandID
;
245 void EditCommandImpl::apply()
248 assert(m_document
->part());
249 assert(state() == NotApplied
);
255 if (!isCompositeStep()) {
256 EditCommand
cmd(this);
257 m_document
->part()->editor()->appliedEditing(cmd
);
261 void EditCommandImpl::unapply()
264 assert(m_document
->part());
265 assert(state() == Applied
);
269 m_state
= NotApplied
;
271 if (!isCompositeStep()) {
272 EditCommand
cmd(this);
273 m_document
->part()->editor()->unappliedEditing(cmd
);
277 void EditCommandImpl::reapply()
280 assert(m_document
->part());
281 assert(state() == NotApplied
);
287 if (!isCompositeStep()) {
288 EditCommand
cmd(this);
289 m_document
->part()->editor()->reappliedEditing(cmd
);
293 void EditCommandImpl::doReapply()
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
323 void EditCommandImpl::setParent(EditCommandImpl
* 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) {
351 for (int i
= m_cmds
.count() - 1; i
>= 0; --i
)
354 setState(NotApplied
);
357 void CompositeEditCommandImpl::doReapply()
359 if (m_cmds
.count() == 0) {
362 QMutableListIterator
<EditCommand
> it(m_cmds
);
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);
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
);
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();
405 insertNodeBefore(insertChild
, child
);
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
);
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
);
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);
545 //==========================================================================================
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
);
556 assert(m_appendChild
);
557 m_appendChild
->ref();
560 AppendNodeCommandImpl::~AppendNodeCommandImpl()
563 m_parentNode
->deref();
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
)
604 ApplyStyleCommandImpl::~ApplyStyleCommandImpl()
610 int ApplyStyleCommandImpl::commandID() const
612 return ApplyStyleCommandID
;
615 void ApplyStyleCommandImpl::doApply()
617 if (endingSelection().state() != Selection::RANGE
)
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
);
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());
641 NodeImpl
*node
= start
.node();
643 if (node
->childNodeCount() == 0 && node
->renderer() && node
->renderer()->isInline()) {
644 NodeImpl
*runStart
= node
;
646 if (runStart
->parentNode() != node
->parentNode() || node
->isHTMLElement() || node
== end
.node() ||
647 (node
->renderer() && !node
->renderer()->isInline())) {
648 applyStyleIfNeeded(runStart
, node
);
651 node
= node
->traverseNextNode();
654 if (node
== end
.node())
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
)
674 case CSS_PROP_FONT_STYLE
:
675 if (elem
->id() == ID_I
)
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.
691 removeNodePreservingChildren(elem
);
694 void ApplyStyleCommandImpl::removeCSSStyle(HTMLElementImpl
*elem
)
698 CSSStyleDeclarationImpl
*decl
= elem
->inlineStyleDecls();
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();
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
);
729 removeCSSStyle(elem
);
731 if (node
== end
.node())
737 bool ApplyStyleCommandImpl::nodeFullySelected(const NodeImpl
*node
) const
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
)));
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();
779 setEndingSelection(Selection(Position(startNode
, start
.offset()), Position(cmd
.node()->previousSibling(), cmd
.node()->previousSibling()->caretMaxOffset())));
780 return cmd
.node()->previousSibling();
785 void ApplyStyleCommandImpl::surroundNodeRangeWithElement(NodeImpl
*startNode
, NodeImpl
*endNode
, ElementImpl
*element
)
791 NodeImpl
*node
= startNode
;
793 NodeImpl
*next
= node
->traverseNextNode();
794 if (node
->childNodeCount() == 0 && node
->renderer() && node
->renderer()->isInline()) {
796 appendNode(element
, node
);
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);
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());
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;
858 styleChange
.cssStyle
+= property
->cssText();
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;
865 styleChange
.cssStyle
+= property
->cssText();
869 styleChange
.cssStyle
+= property
->cssText();
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());
882 pos
= Position(split
.node(), 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
))
895 if (pos
.offset() >= pos
.node()->caretMaxOffset()) {
896 NodeImpl
*nextNode
= pos
.node()->traverseNextNode();
898 Position next
= Position(nextNode
, 0);
899 if (currentlyHasStyle(next
))
905 if (pos
.offset() <= pos
.node()->caretMinOffset()) {
906 NodeImpl
*prevNode
= pos
.node()->traversePreviousNode();
908 Position prev
= Position(prevNode
, prevNode
->maxOffset());
909 if (currentlyHasStyle(prev
))
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())
945 RenderObject
*renderer
= pos
.node()->renderer();
949 TextImpl
*textNode
= static_cast<TextImpl
*>(pos
.node());
950 if (pos
.offset() >= (long)textNode
->length())
953 if (pos
.isLastRenderedPositionInEditableBlock())
956 if (pos
.isFirstRenderedPositionOnLine() || pos
.isLastRenderedPositionOnLine())
959 RenderText
*textRenderer
= static_cast<RenderText
*>(renderer
);
960 for (InlineTextBox
*box
= textRenderer
->firstTextBox(); box
; box
= box
->nextTextBox()) {
961 if (pos
.offset() < box
->m_start
) {
964 if (pos
.offset() >= box
->m_start
&& pos
.offset() < box
->m_start
+ box
->m_len
)
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()<< "]";
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()<< "]";
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
)
997 PositionIterator
it(upstream
);
998 Position deleteStart
= upstream
;
1000 deleteStart
= it
.peekNext();
1001 if (deleteStart
== downstream
)
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
);
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
);
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
);
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
)
1105 Position
pos(selection
.start());
1107 if (!pos
.node()->isTextNode())
1110 TextImpl
*textNode
= static_cast<TextImpl
*>(pos
.node());
1112 if (pos
.offset() == 0) {
1113 PositionIterator
it(pos
);
1114 Position prev
= it
.previous();
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();
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())
1150 const DOMString
&text
= static_cast<TextImpl
*>(it
.current().node())->data();
1151 // EDIT FIXME: signed/unsigned mismatch
1152 if (text
.length() > INT_MAX
)
1154 if (it
.current().offset() < (int)text
.length() && !isWS(text
[it
.current().offset()]))
1157 if (it
.current() == end
)
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
)
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
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());
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())
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());
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()) {
1318 n
= n
->traverseNextNode();
1319 if (d
->renderer() && d
->renderer()->isEditable())
1320 removeNodeAndPrune(d
, startBlock
);
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());
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();
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
)
1375 assert(m_offset
>= 0);
1376 assert(m_count
>= 0);
1381 DeleteTextCommandImpl::~DeleteTextCommandImpl()
1387 int DeleteTextCommandImpl::commandID() const
1389 return DeleteTextCommandID
;
1392 void DeleteTextCommandImpl::doApply()
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()
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
);
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
);
1454 insertNodeBefore(node
, pos
.node());
1457 void InputNewlineCommandImpl::doApply()
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();
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
);
1488 ElementImpl
*extraBreakNode
= document()->createHTMLElement("BR");
1489 // assert(exceptionCode == 0);
1490 insertNodeAfter(extraBreakNode
, nodeToInsert
);
1491 setEndingSelection(Position(extraBreakNode
, 0));
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));
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
)
1540 void InputTextCommandImpl::deleteCharacter()
1542 assert(state() == Applied
);
1544 Selection selection
= endingSelection();
1546 if (!selection
.start().node()->isTextNode())
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();
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());
1605 pos
= Position(textNode
, 0);
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
);
1629 insertNodeBefore(styleElement
, node
);
1630 pos
= Position(editingTextNode
, 0);
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
)
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.
1659 insertSpace(textNode
, offset
);
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
)
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
1687 for (unsigned int i
= offset
; i
< text
.length(); i
++) {
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
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
, " ");
1710 if (text
.length() >= 2 && offset
>= 2 && isNBSP(text
[offset
- 2]) && isNBSP(text
[offset
- 1])) {
1711 // DOM looks like this:
1713 // insert a space between the two nbsps
1714 insertText(textNode
, offset
- 1, " ");
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();
1735 InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl()
1738 m_insertChild
->deref();
1740 m_refChild
->deref();
1743 int InsertNodeBeforeCommandImpl::commandID() const
1745 return InsertNodeBeforeCommandID
;
1748 void InsertNodeBeforeCommandImpl::doApply()
1750 assert(m_insertChild
);
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
);
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
)
1777 assert(m_offset
>= 0);
1778 assert(text
.length() > 0);
1781 m_text
= text
.copy(); // make a copy to ensure that the string never changes
1784 InsertTextCommandImpl::~InsertTextCommandImpl()
1790 int InsertTextCommandImpl::commandID() const
1792 return InsertTextCommandID
;
1795 void InsertTextCommandImpl::doApply()
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()
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
)
1823 assert(m_text1
->nextSibling() == m_text2
);
1824 assert(m_text1
->length() > 0);
1825 assert(m_text2
->length() > 0);
1831 JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl()
1839 int JoinTextNodesCommandImpl::commandID() const
1841 return JoinTextNodesCommandID
;
1844 void JoinTextNodesCommandImpl::doApply()
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()
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
)
1905 deleteCollapsibleWhitespace();
1907 selection
= endingSelection();
1908 assert(!selection
.isEmpty());
1911 // Pasting something that didn't parse or was empty.
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()));
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
1930 NodeImpl
*next
= node
->nextSibling();
1931 insertNodeAfter(node
, beforeNode
);
1937 // Find the last leaf.
1938 NodeImpl
*lastLeaf
= lastChild
;
1940 NodeImpl
*nextChild
= lastLeaf
->lastChild();
1943 lastLeaf
= nextChild
;
1946 if (m_selectReplacement
) {
1947 // Find the first leaf.
1948 NodeImpl
*firstLeaf
= firstChild
;
1950 NodeImpl
*nextChild
= firstLeaf
->firstChild();
1953 firstLeaf
= nextChild
;
1955 // Select what was inserted.
1956 setEndingSelection(Selection(Position(firstLeaf
, firstLeaf
->caretMinOffset()), Position(lastLeaf
, lastLeaf
->caretMaxOffset())));
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();
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)
2016 RemoveCSSPropertyCommandImpl::~RemoveCSSPropertyCommandImpl()
2022 int RemoveCSSPropertyCommandImpl::commandID() const
2024 return RemoveCSSPropertyCommandID
;
2027 void RemoveCSSPropertyCommandImpl::doApply()
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()
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
)
2056 RemoveNodeAttributeCommandImpl::~RemoveNodeAttributeCommandImpl()
2062 int RemoveNodeAttributeCommandImpl::commandID() const
2064 return RemoveNodeAttributeCommandID
;
2067 void RemoveNodeAttributeCommandImpl::doApply()
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()
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();
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
)
2114 RemoveNodeCommandImpl::~RemoveNodeCommandImpl()
2119 m_removeChild
->deref();
2121 m_refChild
->deref();
2124 int RemoveNodeCommandImpl::commandID() const
2126 return RemoveNodeCommandID
;
2129 void RemoveNodeCommandImpl::doApply()
2132 assert(m_removeChild
);
2134 int exceptionCode
= 0;
2135 m_parent
->removeChild(m_removeChild
, exceptionCode
);
2136 assert(exceptionCode
== 0);
2139 void RemoveNodeCommandImpl::doUnapply()
2142 assert(m_removeChild
);
2144 int exceptionCode
= 0;
2146 m_parent
->insertBefore(m_removeChild
, m_refChild
, exceptionCode
);
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
);
2164 RemoveNodeAndPruneCommandImpl::~RemoveNodeAndPruneCommandImpl()
2166 m_pruneNode
->deref();
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
);
2183 if (node
== m_stopNode
|| editableBlock
!= node
->enclosingBlockFlowElement() || !shouldPruneNode(node
))
2186 node
= node
->traversePreviousNode();
2187 removeNode(pruneNode
);
2191 //------------------------------------------------------------------------------------------
2192 // RemoveNodePreservingChildrenCommandImpl
2194 RemoveNodePreservingChildrenCommandImpl::RemoveNodePreservingChildrenCommandImpl(DocumentImpl
*document
, NodeImpl
*node
)
2195 : CompositeEditCommandImpl(document
), m_node(node
)
2201 RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl()
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);
2219 insertNodeBefore(child
, 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
)
2232 assert(!m_value
.isNull());
2235 SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl()
2241 int SetNodeAttributeCommandImpl::commandID() const
2243 return SetNodeAttributeCommandID
;
2246 void SetNodeAttributeCommandImpl::doApply()
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()
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
)
2274 assert(m_text2
->length() > 0);
2279 SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl()
2287 int SplitTextNodeCommandImpl::commandID() const
2289 return SplitTextNodeCommandID
;
2292 void SplitTextNodeCommandImpl::doApply()
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.
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);
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()
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()
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
);
2376 EditCommand lastCommand
= m_cmds
.last();
2377 if (lastCommand
.commandID() == InputTextCommandID
) {
2378 static_cast<InputTextCommand
&>(lastCommand
).input(text
);
2381 InputTextCommand
cmd(document());
2382 applyCommandToComposite(cmd
);
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
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
2423 issueCommandForDeleteKey();
2425 if (m_cmds
.count() == 0) {
2426 issueCommandForDeleteKey();
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) {
2437 else if (lastCommand
.commandID() == InputNewlineCommandID
) {
2438 lastCommand
.unapply();
2439 removeCommand(lastCommand
);
2442 issueCommandForDeleteKey();
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());
2459 setEndingSelection(m_cmds
.last().endingSelection());
2462 //------------------------------------------------------------------------------------------
2464 } // namespace khtml