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"
28 #include "cssproperties.h"
29 #include "css/css_computedstyle.h"
30 #include "css/css_valueimpl.h"
31 #include "dom/css_value.h"
32 #include "dom/dom_position.h"
33 #include "html/html_elementimpl.h"
34 #include "html/html_imageimpl.h"
35 #include "htmlattrs.h"
37 #include "khtml_part.h"
38 #include "khtmlview.h"
40 #include "rendering/render_object.h"
41 #include "rendering/render_style.h"
42 #include "rendering/render_text.h"
43 #include "xml/dom_caretposition.h"
44 #include "xml/dom_docimpl.h"
45 #include "xml/dom_elementimpl.h"
46 #include "xml/dom_positioniterator.h"
47 #include "xml/dom_nodeimpl.h"
48 #include "xml/dom_selection.h"
49 #include "xml/dom_stringimpl.h"
50 #include "xml/dom_textimpl.h"
51 #include "xml/dom2_rangeimpl.h"
54 #include "KWQAssertions.h"
55 #include "KWQLogging.h"
56 #include "KWQKHTMLPart.h"
60 using DOM::CaretPosition;
61 using DOM::CSSComputedStyleDeclarationImpl;
62 using DOM::CSSPrimitiveValue;
63 using DOM::CSSPrimitiveValueImpl;
64 using DOM::CSSProperty;
65 using DOM::CSSStyleDeclarationImpl;
66 using DOM::CSSValueImpl;
67 using DOM::DocumentFragmentImpl;
68 using DOM::DocumentImpl;
70 using DOM::DOMStringImpl;
71 using DOM::EditingTextImpl;
72 using DOM::PositionIterator;
73 using DOM::ElementImpl;
74 using DOM::HTMLElementImpl;
75 using DOM::HTMLImageElementImpl;
76 using DOM::NamedAttrMapImpl;
79 using DOM::NodeListImpl;
84 using DOM::StayInBlock;
86 using DOM::TreeWalkerImpl;
89 #define ASSERT(assertion) ((void)0)
90 #define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
91 #define ASSERT_NOT_REACHED() ((void)0)
92 #define LOG(channel, formatAndArgs...) ((void)0)
93 #define ERROR(formatAndArgs...) ((void)0)
95 #define debugPosition(a,b) ((void)0)
102 static inline bool isNBSP(const QChar &c)
104 return c == QChar(0xa0);
107 static inline bool isWS(const QChar &c)
109 return c.isSpace() && c != QChar(0xa0);
112 static inline bool isWS(const DOMString &text)
114 if (text.length() != 1)
117 return isWS(text[0]);
120 static inline bool isWS(const Position &pos)
125 if (!pos.node()->isTextNode())
128 const DOMString &string = static_cast<TextImpl *>(pos.node())->data();
129 return isWS(string[pos.offset()]);
132 static const int spacesPerTab = 4;
134 static inline bool isTab(const DOMString &text)
136 static QChar tabCharacter = QChar(0x9);
137 if (text.length() != 1)
140 return text[0] == tabCharacter;
143 static DOMString &nonBreakingSpaceString()
145 static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
146 return nonBreakingSpaceString;
149 static DOMString &styleSpanClassString()
151 static DOMString styleSpanClassString = "khtml-style-span";
152 return styleSpanClassString;
155 static void debugPosition(const char *prefix, const Position &pos)
160 LOG(Editing, "%s <empty>", prefix);
162 LOG(Editing, "%s%s %p : %d", prefix, getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
165 //------------------------------------------------------------------------------------------
168 StyleChange::StyleChange(CSSStyleDeclarationImpl *style)
170 init(style, Position());
173 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position)
175 init(style, position);
178 void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
181 m_applyItalic = false;
185 for (QPtrListIterator<CSSProperty> it(*(style->values())); it.current(); ++it) {
186 CSSProperty *property = it.current();
188 // If position is empty or the position passed in already has the
189 // style, just move on.
190 if (position.notEmpty() && currentlyHasStyle(position, property))
193 // Figure out the manner of change that is needed.
194 DOMString valueText(property->value()->cssText());
195 switch (property->id()) {
196 case CSS_PROP_FONT_WEIGHT:
197 if (strcasecmp(valueText, "bold") == 0) {
202 case CSS_PROP_FONT_STYLE:
203 if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) {
204 m_applyItalic = true;
210 styleText += property->cssText().string();
213 m_cssStyle = styleText.stripWhiteSpace();
216 bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
218 ASSERT(pos.notEmpty());
219 CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
222 CSSValueImpl *value = style->getPropertyCSSValue(property->id());
224 return value && strcasecmp(value->cssText(), property->value()->cssText()) == 0;
227 //------------------------------------------------------------------------------------------
230 EditCommandImpl::EditCommandImpl(DocumentImpl *document)
231 : m_document(document), m_state(NotApplied), m_typingStyle(0), m_parent(0)
234 ASSERT(m_document->part());
236 m_startingSelection = m_document->part()->selection();
237 m_endingSelection = m_startingSelection;
240 EditCommandImpl::~EditCommandImpl()
245 m_typingStyle->deref();
248 void EditCommandImpl::apply()
251 ASSERT(m_document->part());
252 ASSERT(state() == NotApplied);
258 // FIXME: Improve typing style.
259 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
260 if (!preservesTypingStyle())
263 if (!isCompositeStep()) {
264 EditCommand cmd(this);
265 m_document->part()->appliedEditing(cmd);
269 void EditCommandImpl::unapply()
272 ASSERT(m_document->part());
273 ASSERT(state() == Applied);
277 m_state = NotApplied;
279 if (!isCompositeStep()) {
280 EditCommand cmd(this);
281 m_document->part()->unappliedEditing(cmd);
285 void EditCommandImpl::reapply()
288 ASSERT(m_document->part());
289 ASSERT(state() == NotApplied);
295 if (!isCompositeStep()) {
296 EditCommand cmd(this);
297 m_document->part()->reappliedEditing(cmd);
301 void EditCommandImpl::doReapply()
306 void EditCommandImpl::setStartingSelection(const Selection &s)
308 for (EditCommandImpl *cmd = this; cmd; cmd = cmd->m_parent.get())
309 cmd->m_startingSelection = s;
312 void EditCommandImpl::setEndingSelection(const Selection &s)
314 for (EditCommandImpl *cmd = this; cmd; cmd = cmd->m_parent.get())
315 cmd->m_endingSelection = s;
318 void EditCommandImpl::assignTypingStyle(DOM::CSSStyleDeclarationImpl *style)
320 CSSStyleDeclarationImpl *old = m_typingStyle;
321 m_typingStyle = style;
323 m_typingStyle->ref();
328 void EditCommandImpl::setTypingStyle(CSSStyleDeclarationImpl *style)
330 // FIXME: Improve typing style.
331 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
332 for (EditCommandImpl *cmd = this; cmd; cmd = cmd->m_parent.get())
333 cmd->assignTypingStyle(style);
336 void EditCommandImpl::markMisspellingsInSelection(const Selection &s)
338 KWQ(document()->part())->markMisspellingsInSelection(s);
341 bool EditCommandImpl::preservesTypingStyle() const
346 bool EditCommandImpl::isInputTextCommand() const
351 bool EditCommandImpl::isTypingCommand() const
356 //------------------------------------------------------------------------------------------
357 // CompositeEditCommandImpl
359 CompositeEditCommandImpl::CompositeEditCommandImpl(DocumentImpl *document)
360 : EditCommandImpl(document)
364 void CompositeEditCommandImpl::doUnapply()
366 if (m_cmds.count() == 0) {
370 for (int i = m_cmds.count() - 1; i >= 0; --i)
371 m_cmds[i]->unapply();
373 setState(NotApplied);
376 void CompositeEditCommandImpl::doReapply()
378 if (m_cmds.count() == 0) {
382 for (QValueList<EditCommand>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
389 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
391 void CompositeEditCommandImpl::applyCommandToComposite(EditCommand &cmd)
393 cmd.setStartingSelection(endingSelection());
394 cmd.setEndingSelection(endingSelection());
400 void CompositeEditCommandImpl::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
402 InsertNodeBeforeCommand cmd(document(), insertChild, refChild);
403 applyCommandToComposite(cmd);
406 void CompositeEditCommandImpl::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
408 if (refChild->parentNode()->lastChild() == refChild) {
409 appendNode(insertChild, refChild->parentNode());
412 ASSERT(refChild->nextSibling());
413 insertNodeBefore(insertChild, refChild->nextSibling());
417 void CompositeEditCommandImpl::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
419 if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
420 NodeImpl *child = refChild->firstChild();
421 for (long i = 0; child && i < offset; i++)
422 child = child->nextSibling();
424 insertNodeBefore(insertChild, child);
426 appendNode(insertChild, refChild);
428 else if (refChild->caretMinOffset() >= offset) {
429 insertNodeBefore(insertChild, refChild);
431 else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
432 splitTextNode(static_cast<TextImpl *>(refChild), offset);
433 insertNodeBefore(insertChild, refChild);
436 insertNodeAfter(insertChild, refChild);
440 void CompositeEditCommandImpl::appendNode(NodeImpl *appendChild, NodeImpl *parent)
442 AppendNodeCommand cmd(document(), appendChild, parent);
443 applyCommandToComposite(cmd);
446 void CompositeEditCommandImpl::removeNode(NodeImpl *removeChild)
448 RemoveNodeCommand cmd(document(), removeChild);
449 applyCommandToComposite(cmd);
452 void CompositeEditCommandImpl::removeNodePreservingChildren(NodeImpl *removeChild)
454 RemoveNodePreservingChildrenCommand cmd(document(), removeChild);
455 applyCommandToComposite(cmd);
458 void CompositeEditCommandImpl::splitTextNode(TextImpl *text, long offset)
460 SplitTextNodeCommand cmd(document(), text, offset);
461 applyCommandToComposite(cmd);
464 void CompositeEditCommandImpl::joinTextNodes(TextImpl *text1, TextImpl *text2)
466 JoinTextNodesCommand cmd(document(), text1, text2);
467 applyCommandToComposite(cmd);
470 void CompositeEditCommandImpl::inputText(const DOMString &text)
472 InputTextCommand cmd(document());
473 applyCommandToComposite(cmd);
477 void CompositeEditCommandImpl::insertText(TextImpl *node, long offset, const DOMString &text)
479 InsertTextCommand cmd(document(), node, offset, text);
480 applyCommandToComposite(cmd);
483 void CompositeEditCommandImpl::deleteText(TextImpl *node, long offset, long count)
485 DeleteTextCommand cmd(document(), node, offset, count);
486 applyCommandToComposite(cmd);
489 void CompositeEditCommandImpl::replaceText(TextImpl *node, long offset, long count, const DOMString &replacementText)
491 DeleteTextCommand deleteCommand(document(), node, offset, count);
492 applyCommandToComposite(deleteCommand);
493 InsertTextCommand insertCommand(document(), node, offset, replacementText);
494 applyCommandToComposite(insertCommand);
497 void CompositeEditCommandImpl::deleteSelection()
499 if (endingSelection().state() == Selection::RANGE) {
500 DeleteSelectionCommand cmd(document());
501 applyCommandToComposite(cmd);
505 void CompositeEditCommandImpl::deleteSelection(const Selection &selection)
507 if (selection.state() == Selection::RANGE) {
508 DeleteSelectionCommand cmd(document(), selection);
509 applyCommandToComposite(cmd);
513 void CompositeEditCommandImpl::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
515 RemoveCSSPropertyCommand cmd(document(), decl, property);
516 applyCommandToComposite(cmd);
519 void CompositeEditCommandImpl::removeNodeAttribute(ElementImpl *element, int attribute)
521 RemoveNodeAttributeCommand cmd(document(), element, attribute);
522 applyCommandToComposite(cmd);
525 void CompositeEditCommandImpl::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
527 SetNodeAttributeCommand cmd(document(), element, attribute, value);
528 applyCommandToComposite(cmd);
531 NodeImpl *CompositeEditCommandImpl::applyTypingStyle(NodeImpl *child) const
533 // FIXME: This function should share code with ApplyStyleCommandImpl::applyStyleIfNeeded
534 // and ApplyStyleCommandImpl::computeStyleChange.
535 // Both function do similar work, and the common parts could be factored out.
537 // FIXME: Improve typing style.
538 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
539 StyleChange styleChange(document()->part()->typingStyle());
541 NodeImpl *childToAppend = child;
542 int exceptionCode = 0;
544 if (styleChange.applyItalic()) {
545 ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
546 ASSERT(exceptionCode == 0);
547 italicElement->appendChild(childToAppend, exceptionCode);
548 ASSERT(exceptionCode == 0);
549 childToAppend = italicElement;
552 if (styleChange.applyBold()) {
553 ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
554 ASSERT(exceptionCode == 0);
555 boldElement->appendChild(childToAppend, exceptionCode);
556 ASSERT(exceptionCode == 0);
557 childToAppend = boldElement;
560 if (styleChange.cssStyle().length() > 0) {
561 ElementImpl *styleElement = document()->createHTMLElement("SPAN", exceptionCode);
562 ASSERT(exceptionCode == 0);
563 styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
564 styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
565 styleElement->appendChild(childToAppend, exceptionCode);
566 ASSERT(exceptionCode == 0);
567 childToAppend = styleElement;
570 return childToAppend;
573 void CompositeEditCommandImpl::deleteUnrenderedText(NodeImpl *node)
578 if (node->isTextNode()) {
579 if (!node->renderer() || !static_cast<RenderText *>(node->renderer())->firstTextBox())
582 TextImpl *text = static_cast<TextImpl *>(node);
583 if (text->caretMinOffset() > 0)
584 deleteText(text, 0, text->caretMinOffset());
585 if ((int)text->length() > text->caretMaxOffset())
586 deleteText(text, text->caretMaxOffset(), text->length() - text->caretMaxOffset());
591 void CompositeEditCommandImpl::deleteUnrenderedText(const Position &pos)
596 Position upstream = pos.upstream(StayInBlock);
597 Position downstream = pos.downstream(StayInBlock);
598 Position block = Position(pos.node()->enclosingBlockFlowElement(), 0);
600 NodeImpl *node = upstream.node();
601 while (node && node != downstream.node()) {
602 NodeImpl *next = node->traverseNextNode();
603 deleteUnrenderedText(node);
606 deleteUnrenderedText(downstream.node());
608 if (pos.node()->inDocument())
609 setEndingSelection(pos);
610 else if (upstream.node()->inDocument())
611 setEndingSelection(upstream);
612 else if (downstream.node()->inDocument())
613 setEndingSelection(downstream);
615 setEndingSelection(block);
618 //==========================================================================================
620 //------------------------------------------------------------------------------------------
621 // AppendNodeCommandImpl
623 AppendNodeCommandImpl::AppendNodeCommandImpl(DocumentImpl *document, NodeImpl *appendChild, NodeImpl *parentNode)
624 : EditCommandImpl(document), m_appendChild(appendChild), m_parentNode(parentNode)
626 ASSERT(m_appendChild);
627 m_appendChild->ref();
629 ASSERT(m_parentNode);
633 AppendNodeCommandImpl::~AppendNodeCommandImpl()
635 ASSERT(m_appendChild);
636 m_appendChild->deref();
638 ASSERT(m_parentNode);
639 m_parentNode->deref();
642 void AppendNodeCommandImpl::doApply()
644 ASSERT(m_appendChild);
645 ASSERT(m_parentNode);
647 int exceptionCode = 0;
648 m_parentNode->appendChild(m_appendChild, exceptionCode);
649 ASSERT(exceptionCode == 0);
652 void AppendNodeCommandImpl::doUnapply()
654 ASSERT(m_appendChild);
655 ASSERT(m_parentNode);
656 ASSERT(state() == Applied);
658 int exceptionCode = 0;
659 m_parentNode->removeChild(m_appendChild, exceptionCode);
660 ASSERT(exceptionCode == 0);
663 //------------------------------------------------------------------------------------------
664 // ApplyStyleCommandImpl
666 ApplyStyleCommandImpl::ApplyStyleCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *style)
667 : CompositeEditCommandImpl(document), m_style(style)
673 ApplyStyleCommandImpl::~ApplyStyleCommandImpl()
679 void ApplyStyleCommandImpl::doApply()
681 if (endingSelection().state() != Selection::RANGE)
684 // adjust to the positions we want to use for applying style
685 Position start(endingSelection().start().downstream(StayInBlock).equivalentRangeCompliantPosition());
686 Position end(endingSelection().end().upstream(StayInBlock));
688 // Remove style from the selection.
689 // Use the upstream position of the start for removing style.
690 // This will ensure we remove all traces of the relevant styles from the selection
691 // and prevent us from adding redundant ones, as described in:
692 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
693 removeStyle(start.upstream(), end);
695 bool splitStart = splitTextAtStartIfNeeded(start, end);
697 start = endingSelection().start();
698 end = endingSelection().end();
700 splitTextAtEndIfNeeded(start, end);
701 start = endingSelection().start();
702 end = endingSelection().end();
705 if (start.node() == end.node()) {
706 // simple case...start and end are the same node
707 applyStyleIfNeeded(start.node(), end.node());
710 NodeImpl *node = start.node();
712 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
713 NodeImpl *runStart = node;
715 NodeImpl *next = node->traverseNextNode();
716 // Break if node is the end node, or if the next node does not fit in with
717 // the current group.
718 if (node == end.node() ||
719 runStart->parentNode() != next->parentNode() ||
720 (next->isHTMLElement() && next->id() != ID_BR) ||
721 (next->renderer() && !next->renderer()->isInline()))
725 // Now apply style to the run we found.
726 applyStyleIfNeeded(runStart, node);
728 if (node == end.node())
730 node = node->traverseNextNode();
735 //------------------------------------------------------------------------------------------
736 // ApplyStyleCommandImpl: style-removal helpers
738 bool ApplyStyleCommandImpl::isHTMLStyleNode(HTMLElementImpl *elem)
740 for (QPtrListIterator<CSSProperty> it(*(style()->values())); it.current(); ++it) {
741 CSSProperty *property = it.current();
742 switch (property->id()) {
743 case CSS_PROP_FONT_WEIGHT:
744 if (elem->id() == ID_B)
747 case CSS_PROP_FONT_STYLE:
748 if (elem->id() == ID_I)
757 void ApplyStyleCommandImpl::removeHTMLStyleNode(HTMLElementImpl *elem)
759 // This node can be removed.
760 // EDIT FIXME: This does not handle the case where the node
761 // has attributes. But how often do people add attributes to <B> tags?
762 // Not so often I think.
764 removeNodePreservingChildren(elem);
767 void ApplyStyleCommandImpl::removeCSSStyle(HTMLElementImpl *elem)
771 CSSStyleDeclarationImpl *decl = elem->inlineStyleDecl();
775 for (QPtrListIterator<CSSProperty> it(*(style()->values())); it.current(); ++it) {
776 CSSProperty *property = it.current();
777 if (decl->getPropertyCSSValue(property->id()))
778 removeCSSProperty(decl, property->id());
781 if (elem->id() == ID_SPAN) {
782 // Check to see if the span is one we added to apply style.
783 // If it is, and there are no more attributes on the span other than our
784 // class marker, remove the span.
785 if (decl->values()->count() == 0) {
786 removeNodeAttribute(elem, ATTR_STYLE);
787 NamedAttrMapImpl *map = elem->attributes();
788 if (map && map->length() == 1 && elem->getAttribute(ATTR_CLASS) == styleSpanClassString())
789 removeNodePreservingChildren(elem);
794 void ApplyStyleCommandImpl::removeStyle(const Position &start, const Position &end)
796 NodeImpl *node = start.node();
798 NodeImpl *next = node->traverseNextNode();
799 if (node->isHTMLElement() && nodeFullySelected(start, node)) {
800 HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
801 if (isHTMLStyleNode(elem))
802 removeHTMLStyleNode(elem);
804 removeCSSStyle(elem);
806 if (node == end.node())
812 bool ApplyStyleCommandImpl::nodeFullySelected(const Position &start, const NodeImpl *node) const
816 if (node == start.node())
817 return start.offset() >= node->caretMaxOffset();
819 for (NodeImpl *child = node->lastChild(); child; child = child->lastChild()) {
820 if (child == start.node())
821 return start.offset() >= child->caretMaxOffset();
824 return !start.node()->isAncestor(node);
827 //------------------------------------------------------------------------------------------
828 // ApplyStyleCommandImpl: style-application helpers
831 bool ApplyStyleCommandImpl::splitTextAtStartIfNeeded(const Position &start, const Position &end)
833 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
834 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
835 TextImpl *text = static_cast<TextImpl *>(start.node());
836 SplitTextNodeCommand cmd(document(), text, start.offset());
837 applyCommandToComposite(cmd);
838 setEndingSelection(Selection(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment)));
844 NodeImpl *ApplyStyleCommandImpl::splitTextAtEndIfNeeded(const Position &start, const Position &end)
846 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
847 TextImpl *text = static_cast<TextImpl *>(end.node());
848 SplitTextNodeCommand cmd(document(), text, end.offset());
849 applyCommandToComposite(cmd);
850 NodeImpl *startNode = start.node() == end.node() ? cmd.node()->previousSibling() : start.node();
852 setEndingSelection(Selection(Position(startNode, start.offset()), Position(cmd.node()->previousSibling(), cmd.node()->previousSibling()->caretMaxOffset())));
853 return cmd.node()->previousSibling();
858 void ApplyStyleCommandImpl::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
864 NodeImpl *node = startNode;
866 NodeImpl *next = node->traverseNextNode();
867 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
869 appendNode(node, element);
877 void ApplyStyleCommandImpl::applyStyleIfNeeded(NodeImpl *startNode, NodeImpl *endNode)
879 // FIXME: This function should share code with CompositeEditCommandImpl::applyTypingStyle.
880 // Both functions do similar work, and the common parts could be factored out.
882 StyleChange styleChange(style(), Position(startNode, 0));
883 int exceptionCode = 0;
885 if (styleChange.cssStyle().length() > 0) {
886 ElementImpl *styleElement = document()->createHTMLElement("SPAN", exceptionCode);
887 ASSERT(exceptionCode == 0);
888 styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
889 styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
890 insertNodeBefore(styleElement, startNode);
891 surroundNodeRangeWithElement(startNode, endNode, styleElement);
894 if (styleChange.applyBold()) {
895 ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
896 ASSERT(exceptionCode == 0);
897 insertNodeBefore(boldElement, startNode);
898 surroundNodeRangeWithElement(startNode, endNode, boldElement);
901 if (styleChange.applyItalic()) {
902 ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
903 ASSERT(exceptionCode == 0);
904 insertNodeBefore(italicElement, startNode);
905 surroundNodeRangeWithElement(startNode, endNode, italicElement);
909 Position ApplyStyleCommandImpl::positionInsertionPoint(Position pos)
911 if (pos.node()->isTextNode() && (pos.offset() > 0 && pos.offset() < pos.node()->maxOffset())) {
912 SplitTextNodeCommand split(document(), static_cast<TextImpl *>(pos.node()), pos.offset());
914 pos = Position(split.node(), 0);
918 // EDIT FIXME: If modified to work with the internals of applying style,
919 // this code can work to optimize cases where a style change is taking place on
920 // a boundary between nodes where one of the nodes has the desired style. In other
921 // words, it is possible for content to be merged into existing nodes rather than adding
922 // additional markup.
923 if (currentlyHasStyle(pos))
927 if (pos.offset() >= pos.node()->caretMaxOffset()) {
928 NodeImpl *nextNode = pos.node()->traverseNextNode();
930 Position next = Position(nextNode, 0);
931 if (currentlyHasStyle(next))
937 if (pos.offset() <= pos.node()->caretMinOffset()) {
938 NodeImpl *prevNode = pos.node()->traversePreviousNode();
940 Position prev = Position(prevNode, prevNode->maxOffset());
941 if (currentlyHasStyle(prev))
950 //------------------------------------------------------------------------------------------
951 // DeleteSelectionCommandImpl
953 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document)
954 : CompositeEditCommandImpl(document), m_hasSelectionToDelete(false)
958 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document, const Selection &selection)
959 : CompositeEditCommandImpl(document), m_selectionToDelete(selection), m_hasSelectionToDelete(true)
963 CSSStyleDeclarationImpl *DeleteSelectionCommandImpl::computeTypingStyle(const Position &pos) const
965 ElementImpl *element = pos.element();
969 ElementImpl *shallowElement = pos.equivalentShallowPosition().element();
973 ElementImpl *parent = Position(shallowElement->parentNode(), 0).element();
977 // Loop from the element up to the shallowElement, building up the
978 // style that this node has that its parent does not.
979 CSSStyleDeclarationImpl *result = document()->createCSSStyleDeclaration();
980 NodeImpl *node = element;
982 // check for an inline style declaration
983 if (node->isHTMLElement()) {
984 CSSStyleDeclarationImpl *s = static_cast<HTMLElementImpl *>(node)->inlineStyleDecl();
986 result->merge(s, false);
988 // check if this is a bold tag
989 if (node->id() == ID_B) {
990 CSSValueImpl *boldValue = result->getPropertyCSSValue(CSS_PROP_FONT_WEIGHT);
992 result->setProperty(CSS_PROP_FONT_WEIGHT, "bold");
994 // check if this is an italic tag
995 if (node->id() == ID_I) {
996 CSSValueImpl *italicValue = result->getPropertyCSSValue(CSS_PROP_FONT_STYLE);
998 result->setProperty(CSS_PROP_FONT_STYLE, "italic");
1000 if (node == shallowElement)
1002 node = node->parentNode();
1008 // This function moves nodes in the block containing startNode to dstBlock, starting
1009 // from startNode and proceeding to the end of the block. Nodes in the block containing
1010 // startNode that appear in document order before startNode are not moved.
1011 // This function is an important helper for deleting selections that cross block
1013 void DeleteSelectionCommandImpl::moveNodesAfterNode(NodeImpl *startNode, NodeImpl *dstNode)
1015 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
1018 NodeImpl *node = startNode == startBlock ? startBlock->firstChild() : startNode;
1019 NodeImpl *refNode = dstNode;
1020 while (node && node->isAncestor(startBlock)) {
1021 NodeImpl *moveNode = node;
1022 node = node->nextSibling();
1023 removeNode(moveNode);
1024 insertNodeAfter(moveNode, refNode);
1028 // If the startBlock no longer has any kids, we may need to deal with adding a BR
1029 // to make the layout come out right. Consider this document:
1035 // Placing the insertion before before the 'T' of 'Two' and hitting delete will
1036 // move the contents of the div to the block containing 'One' and delete the div.
1037 // This will have the side effect of moving 'Three' on to the same line as 'One'
1038 // and 'Two'. This is undesirable. We fix this up by adding a BR before the 'Three'.
1039 // This may not be ideal, but it is better than nothing.
1040 if (!startBlock->firstChild()) {
1041 removeNode(startBlock);
1042 document()->updateLayout();
1043 if (refNode->renderer() && refNode->renderer()->inlineBox() && refNode->renderer()->inlineBox()->nextOnLineExists()) {
1044 int exceptionCode = 0;
1045 ElementImpl *breakNode = document()->createHTMLElement("BR", exceptionCode);
1046 ASSERT(exceptionCode == 0);
1047 insertNodeAfter(breakNode, refNode);
1052 void DeleteSelectionCommandImpl::doApply()
1054 // If selection has not been set to a custom selection when the command was created,
1055 // use the current ending selection.
1056 if (!m_hasSelectionToDelete)
1057 m_selectionToDelete = endingSelection();
1059 if (m_selectionToDelete.state() != Selection::RANGE)
1062 Position upstreamStart(m_selectionToDelete.start().upstream(StayInBlock));
1063 Position downstreamStart(m_selectionToDelete.start().downstream(StayInBlock));
1064 Position upstreamEnd(m_selectionToDelete.end().upstream(StayInBlock));
1065 Position downstreamEnd(m_selectionToDelete.end().downstream(StayInBlock));
1066 Position endingPosition;
1068 // Save away whitespace situation before doing any deletions
1069 Position leading = upstreamStart.leadingWhitespacePosition();
1070 Position trailing = downstreamEnd.trailingWhitespacePosition();
1071 bool trailingValid = true;
1073 debugPosition("upstreamStart ", upstreamStart);
1074 debugPosition("downstreamStart ", downstreamStart);
1075 debugPosition("upstreamEnd ", upstreamEnd);
1076 debugPosition("downstreamEnd ", downstreamEnd);
1077 debugPosition("leading ", leading);
1078 debugPosition("trailing ", trailing);
1080 NodeImpl *startBlock = downstreamStart.node()->enclosingBlockFlowElement();
1081 NodeImpl *endBlock = upstreamEnd.node()->enclosingBlockFlowElement();
1082 if (!startBlock || !endBlock)
1083 // Can't figure out what blocks we're in. This can happen if
1084 // the document structure is not what we are expecting, like if
1085 // the document has no body element, or if the editable block
1086 // has been changed to display: inline. Some day it might
1087 // be nice to be able to deal with this, but for now, bail.
1090 // Figure out the typing style in effect before the delete is done.
1091 // FIXME: Improve typing style.
1092 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
1093 CSSComputedStyleDeclarationImpl *computedStyle = downstreamStart.computedStyle();
1094 computedStyle->ref();
1095 CSSStyleDeclarationImpl *style = computedStyle->copyInheritableProperties();
1097 computedStyle->deref();
1099 NodeImpl *startNode = upstreamStart.node();
1100 int startOffset = upstreamStart.offset();
1101 if (startOffset >= startNode->caretMaxOffset()) {
1102 // None of the first node is to be deleted, so move to next.
1103 startNode = startNode->traverseNextNode();
1107 if (startNode == downstreamEnd.node()) {
1108 // handle delete in one node
1109 if (!startNode->renderer() ||
1110 (startOffset <= startNode->caretMinOffset() && downstreamEnd.offset() >= startNode->caretMaxOffset())) {
1112 removeNode(startNode);
1114 else if (downstreamEnd.offset() - startOffset > 0) {
1115 // in a text node that needs to be trimmed
1116 TextImpl *text = static_cast<TextImpl *>(startNode);
1117 deleteText(text, startOffset, downstreamEnd.offset() - startOffset);
1118 trailingValid = false;
1122 NodeImpl *node = startNode;
1124 if (startOffset > 0) {
1125 // in a text node that needs to be trimmed
1126 TextImpl *text = static_cast<TextImpl *>(node);
1127 deleteText(text, startOffset, text->length() - startOffset);
1128 node = node->traverseNextNode();
1131 // handle deleting all nodes that are completely selected
1132 while (node && node != downstreamEnd.node()) {
1133 if (!downstreamEnd.node()->isAncestor(node)) {
1134 NodeImpl *nextNode = node->traverseNextSibling();
1139 NodeImpl *n = node->lastChild();
1140 while (n && n->lastChild())
1142 if (n == downstreamEnd.node() && downstreamEnd.offset() >= downstreamEnd.node()->caretMaxOffset()) {
1143 NodeImpl *nextNode = node->traverseNextSibling();
1148 node = node->traverseNextNode();
1153 if (downstreamEnd.node() != startNode && downstreamEnd.node()->inDocument() && downstreamEnd.offset() >= downstreamEnd.node()->caretMinOffset()) {
1154 if (downstreamEnd.offset() >= downstreamEnd.node()->caretMaxOffset()) {
1155 // need to delete whole node
1156 // we can get here if this is the last node in the block
1157 removeNode(downstreamEnd.node());
1158 trailingValid = false;
1161 // in a text node that needs to be trimmed
1162 TextImpl *text = static_cast<TextImpl *>(downstreamEnd.node());
1163 if (downstreamEnd.offset() > 0) {
1164 deleteText(text, 0, downstreamEnd.offset());
1165 trailingValid = false;
1168 if (!downstreamEnd.node()->inDocument() && downstreamEnd.node()->inDocument())
1169 endingPosition = Position(downstreamEnd.node(), 0);
1173 // Do block merge if start and end of selection are in different blocks.
1174 if (endBlock != startBlock && downstreamEnd.node()->inDocument()) {
1175 LOG(Editing, "merging content to start block");
1176 // cross blocks and delete unrenderered text from the destination location
1177 deleteUnrenderedText(upstreamStart.upstream());
1178 // stay in this block and delete unrenderered text from the source location
1179 deleteUnrenderedText(upstreamStart);
1180 // move the downstream end position's node, and all other nodes in its
1181 // block to the start block.
1182 moveNodesAfterNode(downstreamEnd.node(), upstreamStart.node());
1185 // Figure out where the end position should be
1186 if (endingPosition.notEmpty())
1187 goto FixupWhitespace;
1189 endingPosition = upstreamStart;
1190 if (endingPosition.node()->inDocument())
1191 goto FixupWhitespace;
1193 endingPosition = downstreamEnd;
1194 if (endingPosition.node()->inDocument())
1195 goto FixupWhitespace;
1197 endingPosition = Position(startBlock, 0);
1198 if (endingPosition.node()->inDocument())
1199 goto FixupWhitespace;
1201 endingPosition = Position(endBlock, 0);
1202 if (endingPosition.node()->inDocument())
1203 goto FixupWhitespace;
1205 endingPosition = Position(document()->documentElement(), 0);
1207 // Perform whitespace fixup
1210 if (leading.notEmpty() || trailing.notEmpty())
1211 document()->updateLayout();
1213 debugPosition("endingPosition ", endingPosition);
1215 if (leading.notEmpty() && !leading.isRenderedCharacter()) {
1216 LOG(Editing, "replace leading");
1217 TextImpl *textNode = static_cast<TextImpl *>(leading.node());
1218 replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
1221 if (trailing.notEmpty()) {
1222 if (trailingValid) {
1223 if (!trailing.isRenderedCharacter()) {
1224 LOG(Editing, "replace trailing [valid]");
1225 TextImpl *textNode = static_cast<TextImpl *>(trailing.node());
1226 replaceText(textNode, trailing.offset(), 1, nonBreakingSpaceString());
1230 Position pos = endingPosition.downstream(StayInBlock);
1231 pos = Position(pos.node(), pos.offset() - 1);
1232 if (isWS(pos) && !pos.isRenderedCharacter()) {
1233 LOG(Editing, "replace trailing [invalid]");
1234 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1235 replaceText(textNode, pos.offset(), 1, nonBreakingSpaceString());
1236 endingPosition = pos;
1241 // Compute the difference between the style before the delete and the style now
1242 // after the delete has been done. Set this style on the part, so other editing
1243 // commands being composed with this one will work, and also cache it on the command,
1244 // so the KHTMLPart::appliedEditing can set it after the whole composite command
1246 // FIXME: Improve typing style.
1247 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
1248 CSSComputedStyleDeclarationImpl endingStyle(endingPosition.node());
1249 endingStyle.diff(style);
1250 document()->part()->setTypingStyle(style);
1251 setTypingStyle(style);
1254 setEndingSelection(endingPosition);
1257 bool DeleteSelectionCommandImpl::preservesTypingStyle() const
1262 //------------------------------------------------------------------------------------------
1263 // DeleteTextCommandImpl
1265 DeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, long count)
1266 : EditCommandImpl(document), m_node(node), m_offset(offset), m_count(count)
1269 ASSERT(m_offset >= 0);
1270 ASSERT(m_offset < (long)m_node->length());
1271 ASSERT(m_count >= 0);
1276 DeleteTextCommandImpl::~DeleteTextCommandImpl()
1282 void DeleteTextCommandImpl::doApply()
1286 int exceptionCode = 0;
1287 m_text = m_node->substringData(m_offset, m_count, exceptionCode);
1288 ASSERT(exceptionCode == 0);
1290 m_node->deleteData(m_offset, m_count, exceptionCode);
1291 ASSERT(exceptionCode == 0);
1294 void DeleteTextCommandImpl::doUnapply()
1297 ASSERT(!m_text.isEmpty());
1299 int exceptionCode = 0;
1300 m_node->insertData(m_offset, m_text, exceptionCode);
1301 ASSERT(exceptionCode == 0);
1304 //------------------------------------------------------------------------------------------
1305 // InputNewlineCommandImpl
1307 InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document)
1308 : CompositeEditCommandImpl(document)
1312 void InputNewlineCommandImpl::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
1314 // Insert the BR after the caret position. In the case the
1315 // position is a block, do an append. We don't want to insert
1316 // the BR *after* the block.
1317 Position upstream(pos.upstream(StayInBlock));
1318 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
1319 if (cb == pos.node())
1320 appendNode(node, cb);
1322 insertNodeAfter(node, pos.node());
1325 void InputNewlineCommandImpl::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
1327 // Insert the BR after the caret position. In the case the
1328 // position is a block, do an append. We don't want to insert
1329 // the BR *before* the block.
1330 Position upstream(pos.upstream(StayInBlock));
1331 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
1332 if (cb == pos.node())
1333 appendNode(node, cb);
1335 insertNodeBefore(node, pos.node());
1338 void InputNewlineCommandImpl::doApply()
1341 deleteUnrenderedText(endingSelection().start());
1343 Selection selection = endingSelection();
1345 int exceptionCode = 0;
1346 ElementImpl *breakNode = document()->createHTMLElement("BR", exceptionCode);
1347 ASSERT(exceptionCode == 0);
1349 NodeImpl *nodeToInsert = breakNode;
1351 // Handle the case where there is a typing style.
1352 // FIXME: Improve typing style.
1353 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
1354 CSSStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
1355 if (typingStyle && typingStyle->length() > 0)
1356 nodeToInsert = applyTypingStyle(breakNode);
1358 Position pos(selection.start().upstream(StayInBlock));
1359 bool atStart = pos.offset() <= pos.node()->caretMinOffset();
1360 bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
1361 bool atEndOfBlock = CaretPosition(pos).isLastInBlock();
1364 LOG(Editing, "input newline case 1");
1365 // Check for a trailing BR. If there isn't one, we'll need to insert an "extra" one.
1366 // This makes the "real" BR we want to insert appear in the rendering without any
1367 // significant side effects (and no real worries either since you can't arrow past
1369 if (pos.node()->id() == ID_BR && pos.offset() == 0) {
1370 // Already placed in a trailing BR. Insert "real" BR before it and leave the selection alone.
1371 insertNodeBefore(nodeToInsert, pos.node());
1374 NodeImpl *next = pos.node()->traverseNextNode();
1375 bool hasTrailingBR = next && next->id() == ID_BR && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
1376 insertNodeAfterPosition(nodeToInsert, pos);
1377 if (hasTrailingBR) {
1378 setEndingSelection(Position(next, 0));
1381 // Insert an "extra" BR at the end of the block.
1382 ElementImpl *extraBreakNode = document()->createHTMLElement("BR", exceptionCode);
1383 ASSERT(exceptionCode == 0);
1384 insertNodeAfter(extraBreakNode, nodeToInsert);
1385 setEndingSelection(Position(extraBreakNode, 0));
1390 LOG(Editing, "input newline case 2");
1391 // Insert node before downstream position, and place caret there as well.
1392 Position endingPosition = pos.downstream(StayInBlock);
1393 insertNodeBeforePosition(nodeToInsert, endingPosition);
1394 setEndingSelection(endingPosition);
1397 LOG(Editing, "input newline case 3");
1398 // Insert BR after this node. Place caret in the position that is downstream
1399 // of the current position, reckoned before inserting the BR in between.
1400 Position endingPosition = pos.downstream(StayInBlock);
1401 insertNodeAfterPosition(nodeToInsert, pos);
1402 setEndingSelection(endingPosition);
1405 // Split a text node
1406 LOG(Editing, "input newline case 4");
1407 ASSERT(pos.node()->isTextNode());
1409 // See if there is trailing whitespace we need to consider
1410 // Note: leading whitespace just works. Blame the web.
1411 Position trailing = pos.downstream(StayInBlock).trailingWhitespacePosition();
1414 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1415 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
1416 deleteText(textNode, 0, pos.offset());
1417 insertNodeBefore(textBeforeNode, textNode);
1418 insertNodeBefore(nodeToInsert, textNode);
1419 Position endingPosition = Position(textNode, 0);
1421 // Handle whitespace that occurs after the split
1422 document()->updateLayout();
1423 if (trailing.notEmpty() && !endingPosition.isRenderedCharacter()) {
1424 // Clear out all whitespace and insert one non-breaking space
1425 deleteUnrenderedText(endingPosition);
1426 insertText(textNode, 0, nonBreakingSpaceString());
1429 setEndingSelection(endingPosition);
1433 //------------------------------------------------------------------------------------------
1434 // InputTextCommandImpl
1436 InputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document)
1437 : CompositeEditCommandImpl(document), m_charactersAdded(0)
1441 void InputTextCommandImpl::doApply()
1445 void InputTextCommandImpl::deleteCharacter()
1447 ASSERT(state() == Applied);
1449 Selection selection = endingSelection();
1451 if (!selection.start().node()->isTextNode())
1454 int exceptionCode = 0;
1455 int offset = selection.start().offset() - 1;
1456 if (offset >= selection.start().node()->caretMinOffset()) {
1457 TextImpl *textNode = static_cast<TextImpl *>(selection.start().node());
1458 textNode->deleteData(offset, 1, exceptionCode);
1459 ASSERT(exceptionCode == 0);
1460 selection = Selection(Position(textNode, offset));
1461 setEndingSelection(selection);
1462 m_charactersAdded--;
1466 Position InputTextCommandImpl::prepareForTextInsertion(bool adjustDownstream)
1468 // Prepare for text input by looking at the current position.
1469 // It may be necessary to insert a text node to receive characters.
1470 Selection selection = endingSelection();
1471 ASSERT(selection.state() == Selection::CARET);
1473 Position pos = selection.start();
1474 if (adjustDownstream)
1475 pos = pos.downstream(StayInBlock);
1477 pos = pos.upstream(StayInBlock);
1479 if (!pos.node()->isTextNode()) {
1480 NodeImpl *textNode = document()->createEditingTextNode("");
1481 NodeImpl *nodeToInsert = textNode;
1483 // Handle the case where there is a typing style.
1484 // FIXME: Improve typing style.
1485 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
1486 CSSStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
1487 if (typingStyle && typingStyle->length() > 0)
1488 nodeToInsert = applyTypingStyle(textNode);
1490 // Now insert the node in the right place
1491 if (pos.node()->isEditableBlock()) {
1492 LOG(Editing, "prepareForTextInsertion case 1");
1493 appendNode(nodeToInsert, pos.node());
1495 else if (pos.node()->id() == ID_BR && pos.offset() == 1) {
1496 LOG(Editing, "prepareForTextInsertion case 2");
1497 insertNodeAfter(nodeToInsert, pos.node());
1499 else if (pos.node()->caretMinOffset() == pos.offset()) {
1500 LOG(Editing, "prepareForTextInsertion case 3");
1501 insertNodeBefore(nodeToInsert, pos.node());
1503 else if (pos.node()->caretMaxOffset() == pos.offset()) {
1504 LOG(Editing, "prepareForTextInsertion case 4");
1505 insertNodeAfter(nodeToInsert, pos.node());
1508 ASSERT_NOT_REACHED();
1510 pos = Position(textNode, 0);
1513 // Handle the case where there is a typing style.
1514 // FIXME: Improve typing style.
1515 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
1516 CSSStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
1517 if (typingStyle && typingStyle->length() > 0) {
1518 if (pos.node()->isTextNode() && pos.offset() > pos.node()->caretMinOffset() && pos.offset() < pos.node()->caretMaxOffset()) {
1519 // Need to split current text node in order to insert a span.
1520 TextImpl *text = static_cast<TextImpl *>(pos.node());
1521 SplitTextNodeCommand cmd(document(), text, pos.offset());
1522 applyCommandToComposite(cmd);
1523 setEndingSelection(Position(cmd.node(), 0));
1526 TextImpl *editingTextNode = document()->createEditingTextNode("");
1527 NodeImpl *node = endingSelection().start().upstream(StayInBlock).node();
1528 if (node->isBlockFlow())
1529 insertNodeAt(applyTypingStyle(editingTextNode), node, 0);
1531 insertNodeAfter(applyTypingStyle(editingTextNode), node);
1532 pos = Position(editingTextNode, 0);
1538 void InputTextCommandImpl::input(const DOMString &text, bool selectInsertedText)
1540 Selection selection = endingSelection();
1541 bool adjustDownstream = selection.start().downstream(StayInBlock).isFirstRenderedPositionOnLine();
1543 // Delete the current selection, or collapse whitespace, as needed
1544 if (selection.state() == Selection::RANGE)
1547 deleteUnrenderedText(endingSelection().start());
1549 // Make sure the document is set up to receive text
1550 Position pos = prepareForTextInsertion(adjustDownstream);
1552 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1553 long offset = pos.offset();
1555 // These are temporary implementations for inserting adjoining spaces
1556 // into a document. We are working on a CSS-related whitespace solution
1557 // that will replace this some day. We hope.
1559 // Treat a tab like a number of spaces. This seems to be the HTML editing convention,
1560 // although the number of spaces varies (we choose four spaces).
1561 // Note that there is no attempt to make this work like a real tab stop, it is merely
1562 // a set number of spaces. This also seems to be the HTML editing convention.
1563 for (int i = 0; i < spacesPerTab; i++) {
1564 insertSpace(textNode, offset);
1565 document()->updateLayout();
1567 if (selectInsertedText)
1568 setEndingSelection(Selection(Position(textNode, offset), Position(textNode, offset + spacesPerTab)));
1570 setEndingSelection(Position(textNode, offset + spacesPerTab));
1571 m_charactersAdded += spacesPerTab;
1573 else if (isWS(text)) {
1574 insertSpace(textNode, offset);
1575 if (selectInsertedText)
1576 setEndingSelection(Selection(Position(textNode, offset), Position(textNode, offset + 1)));
1578 setEndingSelection(Position(textNode, offset + 1));
1579 m_charactersAdded++;
1582 const DOMString &existingText = textNode->data();
1583 if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isWS(existingText[offset - 2])) {
1584 // DOM looks like this:
1585 // character nbsp caret
1586 // As we are about to insert a non-whitespace character at the caret
1587 // convert the nbsp to a regular space.
1588 // EDIT FIXME: This needs to be improved some day to convert back only
1589 // those nbsp's added by the editor to make rendering come out right.
1590 replaceText(textNode, offset - 1, 1, " ");
1592 insertText(textNode, offset, text);
1593 if (selectInsertedText)
1594 setEndingSelection(Selection(Position(textNode, offset), Position(textNode, offset + text.length())));
1596 setEndingSelection(Position(textNode, offset + text.length()));
1597 m_charactersAdded += text.length();
1601 void InputTextCommandImpl::insertSpace(TextImpl *textNode, unsigned long offset)
1605 DOMString text(textNode->data());
1607 // count up all spaces and newlines in front of the caret
1608 // delete all collapsed ones
1609 // this will work out OK since the offset we have been passed has been upstream-ized
1611 for (unsigned int i = offset; i < text.length(); i++) {
1618 // By checking the character at the downstream position, we can
1619 // check if there is a rendered WS at the caret
1620 Position pos(textNode, offset);
1621 Position downstream = pos.downstream();
1622 if (downstream.offset() < (long)text.length() && isWS(text[downstream.offset()]))
1623 count--; // leave this WS in
1625 deleteText(textNode, offset, count);
1628 if (offset > 0 && offset <= text.length() - 1 && !isWS(text[offset]) && !isWS(text[offset - 1])) {
1629 // insert a "regular" space
1630 insertText(textNode, offset, " ");
1634 if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
1635 // DOM looks like this:
1637 // insert a space between the two nbsps
1638 insertText(textNode, offset - 1, " ");
1643 insertText(textNode, offset, nonBreakingSpaceString());
1646 bool InputTextCommandImpl::isInputTextCommand() const
1651 //------------------------------------------------------------------------------------------
1652 // InsertNodeBeforeCommandImpl
1654 InsertNodeBeforeCommandImpl::InsertNodeBeforeCommandImpl(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
1655 : EditCommandImpl(document), m_insertChild(insertChild), m_refChild(refChild)
1657 ASSERT(m_insertChild);
1658 m_insertChild->ref();
1664 InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl()
1666 ASSERT(m_insertChild);
1667 m_insertChild->deref();
1670 m_refChild->deref();
1673 void InsertNodeBeforeCommandImpl::doApply()
1675 ASSERT(m_insertChild);
1677 ASSERT(m_refChild->parentNode());
1679 int exceptionCode = 0;
1680 m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
1681 ASSERT(exceptionCode == 0);
1684 void InsertNodeBeforeCommandImpl::doUnapply()
1686 ASSERT(m_insertChild);
1688 ASSERT(m_refChild->parentNode());
1690 int exceptionCode = 0;
1691 m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
1692 ASSERT(exceptionCode == 0);
1695 //------------------------------------------------------------------------------------------
1696 // InsertTextCommandImpl
1698 InsertTextCommandImpl::InsertTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
1699 : EditCommandImpl(document), m_node(node), m_offset(offset)
1702 ASSERT(m_offset >= 0);
1705 m_text = text.copy(); // make a copy to ensure that the string never changes
1708 InsertTextCommandImpl::~InsertTextCommandImpl()
1714 void InsertTextCommandImpl::doApply()
1718 if (m_text.isEmpty())
1721 int exceptionCode = 0;
1722 m_node->insertData(m_offset, m_text, exceptionCode);
1723 ASSERT(exceptionCode == 0);
1726 void InsertTextCommandImpl::doUnapply()
1729 ASSERT(!m_text.isEmpty());
1731 if (m_text.isEmpty())
1734 int exceptionCode = 0;
1735 m_node->deleteData(m_offset, m_text.length(), exceptionCode);
1736 ASSERT(exceptionCode == 0);
1739 //------------------------------------------------------------------------------------------
1740 // JoinTextNodesCommandImpl
1742 JoinTextNodesCommandImpl::JoinTextNodesCommandImpl(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
1743 : EditCommandImpl(document), m_text1(text1), m_text2(text2)
1747 ASSERT(m_text1->nextSibling() == m_text2);
1748 ASSERT(m_text1->length() > 0);
1749 ASSERT(m_text2->length() > 0);
1755 JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl()
1763 void JoinTextNodesCommandImpl::doApply()
1767 ASSERT(m_text1->nextSibling() == m_text2);
1769 int exceptionCode = 0;
1770 m_text2->insertData(0, m_text1->data(), exceptionCode);
1771 ASSERT(exceptionCode == 0);
1773 m_text2->parentNode()->removeChild(m_text1, exceptionCode);
1774 ASSERT(exceptionCode == 0);
1776 m_offset = m_text1->length();
1779 void JoinTextNodesCommandImpl::doUnapply()
1782 ASSERT(m_offset > 0);
1784 int exceptionCode = 0;
1786 m_text2->deleteData(0, m_offset, exceptionCode);
1787 ASSERT(exceptionCode == 0);
1789 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
1790 ASSERT(exceptionCode == 0);
1792 ASSERT(m_text2->previousSibling()->isTextNode());
1793 ASSERT(m_text2->previousSibling() == m_text1);
1796 //------------------------------------------------------------------------------------------
1797 // ReplaceSelectionCommandImpl
1799 ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement)
1800 : CompositeEditCommandImpl(document), m_fragment(fragment), m_selectReplacement(selectReplacement)
1806 ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl()
1809 m_fragment->deref();
1812 void ReplaceSelectionCommandImpl::doApply()
1814 NodeImpl *firstChild = m_fragment->firstChild();
1815 NodeImpl *lastChild = m_fragment->lastChild();
1817 Selection selection = endingSelection();
1819 // Delete the current selection, or collapse whitespace, as needed
1820 if (selection.state() == Selection::RANGE)
1823 // This command does not use any typing style that is set as a residual effect of
1825 // FIXME: Improve typing style.
1826 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
1827 document()->part()->clearTypingStyle();
1830 selection = endingSelection();
1831 ASSERT(!selection.isEmpty());
1834 // Pasting something that didn't parse or was empty.
1836 } else if (firstChild == lastChild && firstChild->isTextNode()) {
1837 // Simple text paste. Treat as if the text were typed.
1838 Position upstreamStart(selection.start().upstream(StayInBlock));
1839 inputText(static_cast<TextImpl *>(firstChild)->data());
1840 if (m_selectReplacement) {
1841 // Select what was inserted.
1842 setEndingSelection(Selection(selection.base(), endingSelection().extent()));
1845 // Mark misspellings in the inserted content.
1846 markMisspellingsInSelection(Selection(upstreamStart, endingSelection().extent()));
1850 // HTML fragment paste.
1851 NodeImpl *beforeNode = firstChild;
1852 NodeImpl *node = firstChild->nextSibling();
1854 insertNodeAt(firstChild, selection.start().node(), selection.start().offset());
1856 // Insert the nodes from the fragment
1858 NodeImpl *next = node->nextSibling();
1859 insertNodeAfter(node, beforeNode);
1865 // Find the last leaf.
1866 NodeImpl *lastLeaf = lastChild;
1868 NodeImpl *nextChild = lastLeaf->lastChild();
1871 lastLeaf = nextChild;
1874 // Find the first leaf.
1875 NodeImpl *firstLeaf = firstChild;
1877 NodeImpl *nextChild = firstLeaf->firstChild();
1880 firstLeaf = nextChild;
1883 Selection replacementSelection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset()));
1884 if (m_selectReplacement) {
1885 // Select what was inserted.
1886 setEndingSelection(replacementSelection);
1889 // Place the cursor after what was inserted, and mark misspellings in the inserted content.
1890 selection = Selection(Position(lastLeaf, lastLeaf->caretMaxOffset()));
1891 setEndingSelection(selection);
1892 markMisspellingsInSelection(replacementSelection);
1897 //------------------------------------------------------------------------------------------
1898 // MoveSelectionCommandImpl
1900 MoveSelectionCommandImpl::MoveSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position)
1901 : CompositeEditCommandImpl(document), m_fragment(fragment), m_position(position)
1907 MoveSelectionCommandImpl::~MoveSelectionCommandImpl()
1910 m_fragment->deref();
1913 void MoveSelectionCommandImpl::doApply()
1915 Selection selection = endingSelection();
1916 ASSERT(selection.state() == Selection::RANGE);
1918 // Update the position otherwise it may become invalid after the selection is deleted.
1919 NodeImpl *positionNode = m_position.node();
1920 long positionOffset = m_position.offset();
1921 Position selectionEnd = selection.end();
1922 long selectionEndOffset = selectionEnd.offset();
1923 if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) {
1924 positionOffset -= selectionEndOffset;
1925 Position selectionStart = selection.start();
1926 if (selectionStart.node() == positionNode) {
1927 positionOffset += selectionStart.offset();
1933 setEndingSelection(Position(positionNode, positionOffset));
1934 ReplaceSelectionCommand cmd(document(), m_fragment, true);
1935 applyCommandToComposite(cmd);
1938 //------------------------------------------------------------------------------------------
1939 // RemoveCSSPropertyCommandImpl
1941 RemoveCSSPropertyCommandImpl::RemoveCSSPropertyCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property)
1942 : EditCommandImpl(document), m_decl(decl), m_property(property), m_important(false)
1948 RemoveCSSPropertyCommandImpl::~RemoveCSSPropertyCommandImpl()
1954 void RemoveCSSPropertyCommandImpl::doApply()
1958 m_oldValue = m_decl->getPropertyValue(m_property);
1959 ASSERT(!m_oldValue.isNull());
1961 m_important = m_decl->getPropertyPriority(m_property);
1962 m_decl->removeProperty(m_property);
1965 void RemoveCSSPropertyCommandImpl::doUnapply()
1968 ASSERT(!m_oldValue.isNull());
1970 m_decl->setProperty(m_property, m_oldValue, m_important);
1973 //------------------------------------------------------------------------------------------
1974 // RemoveNodeAttributeCommandImpl
1976 RemoveNodeAttributeCommandImpl::RemoveNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute)
1977 : EditCommandImpl(document), m_element(element), m_attribute(attribute)
1983 RemoveNodeAttributeCommandImpl::~RemoveNodeAttributeCommandImpl()
1989 void RemoveNodeAttributeCommandImpl::doApply()
1993 m_oldValue = m_element->getAttribute(m_attribute);
1994 ASSERT(!m_oldValue.isNull());
1996 int exceptionCode = 0;
1997 m_element->removeAttribute(m_attribute, exceptionCode);
1998 ASSERT(exceptionCode == 0);
2001 void RemoveNodeAttributeCommandImpl::doUnapply()
2004 ASSERT(!m_oldValue.isNull());
2006 int exceptionCode = 0;
2007 m_element->setAttribute(m_attribute, m_oldValue.implementation(), exceptionCode);
2008 ASSERT(exceptionCode == 0);
2011 //------------------------------------------------------------------------------------------
2012 // RemoveNodeCommandImpl
2014 RemoveNodeCommandImpl::RemoveNodeCommandImpl(DocumentImpl *document, NodeImpl *removeChild)
2015 : EditCommandImpl(document), m_parent(0), m_removeChild(removeChild), m_refChild(0)
2017 ASSERT(m_removeChild);
2018 m_removeChild->ref();
2020 m_parent = m_removeChild->parentNode();
2024 m_refChild = m_removeChild->nextSibling();
2029 RemoveNodeCommandImpl::~RemoveNodeCommandImpl()
2034 ASSERT(m_removeChild);
2035 m_removeChild->deref();
2038 m_refChild->deref();
2041 void RemoveNodeCommandImpl::doApply()
2044 ASSERT(m_removeChild);
2046 int exceptionCode = 0;
2047 m_parent->removeChild(m_removeChild, exceptionCode);
2048 ASSERT(exceptionCode == 0);
2051 void RemoveNodeCommandImpl::doUnapply()
2054 ASSERT(m_removeChild);
2056 int exceptionCode = 0;
2057 m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
2058 ASSERT(exceptionCode == 0);
2061 //------------------------------------------------------------------------------------------
2062 // RemoveNodePreservingChildrenCommandImpl
2064 RemoveNodePreservingChildrenCommandImpl::RemoveNodePreservingChildrenCommandImpl(DocumentImpl *document, NodeImpl *node)
2065 : CompositeEditCommandImpl(document), m_node(node)
2071 RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl()
2077 void RemoveNodePreservingChildrenCommandImpl::doApply()
2079 while (NodeImpl* curr = node()->firstChild()) {
2081 insertNodeBefore(curr, node());
2086 //------------------------------------------------------------------------------------------
2087 // SetNodeAttributeCommandImpl
2089 SetNodeAttributeCommandImpl::SetNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value)
2090 : EditCommandImpl(document), m_element(element), m_attribute(attribute), m_value(value)
2094 ASSERT(!m_value.isNull());
2097 SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl()
2103 void SetNodeAttributeCommandImpl::doApply()
2106 ASSERT(!m_value.isNull());
2108 int exceptionCode = 0;
2109 m_oldValue = m_element->getAttribute(m_attribute);
2110 m_element->setAttribute(m_attribute, m_value.implementation(), exceptionCode);
2111 ASSERT(exceptionCode == 0);
2114 void SetNodeAttributeCommandImpl::doUnapply()
2117 ASSERT(!m_oldValue.isNull());
2119 int exceptionCode = 0;
2120 m_element->setAttribute(m_attribute, m_oldValue.implementation(), exceptionCode);
2121 ASSERT(exceptionCode == 0);
2124 //------------------------------------------------------------------------------------------
2125 // SplitTextNodeCommandImpl
2127 SplitTextNodeCommandImpl::SplitTextNodeCommandImpl(DocumentImpl *document, TextImpl *text, long offset)
2128 : EditCommandImpl(document), m_text1(0), m_text2(text), m_offset(offset)
2131 ASSERT(m_text2->length() > 0);
2136 SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl()
2145 void SplitTextNodeCommandImpl::doApply()
2148 ASSERT(m_offset > 0);
2150 int exceptionCode = 0;
2152 // EDIT FIXME: This should use better smarts for figuring out which portion
2153 // of the split to copy (based on their comparitive sizes). We should also
2154 // just use the DOM's splitText function.
2157 // create only if needed.
2158 // if reapplying, this object will already exist.
2159 m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode));
2160 ASSERT(exceptionCode == 0);
2165 m_text2->deleteData(0, m_offset, exceptionCode);
2166 ASSERT(exceptionCode == 0);
2168 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
2169 ASSERT(exceptionCode == 0);
2171 ASSERT(m_text2->previousSibling()->isTextNode());
2172 ASSERT(m_text2->previousSibling() == m_text1);
2175 void SplitTextNodeCommandImpl::doUnapply()
2180 ASSERT(m_text1->nextSibling() == m_text2);
2182 int exceptionCode = 0;
2183 m_text2->insertData(0, m_text1->data(), exceptionCode);
2184 ASSERT(exceptionCode == 0);
2186 m_text2->parentNode()->removeChild(m_text1, exceptionCode);
2187 ASSERT(exceptionCode == 0);
2189 m_offset = m_text1->length();
2192 //------------------------------------------------------------------------------------------
2193 // TypingCommandImpl
2195 TypingCommandImpl::TypingCommandImpl(DocumentImpl *document, TypingCommand::ETypingCommand commandType, const DOM::DOMString &textToInsert, bool selectInsertedText)
2196 : CompositeEditCommandImpl(document), m_commandType(commandType), m_textToInsert(textToInsert), m_openForMoreTyping(true), m_applyEditing(false), m_selectInsertedText(selectInsertedText)
2200 void TypingCommandImpl::doApply()
2202 if (endingSelection().state() == Selection::NONE)
2205 switch (m_commandType) {
2206 case TypingCommand::DeleteKey:
2209 case TypingCommand::InsertText:
2210 insertText(m_textToInsert, m_selectInsertedText);
2212 case TypingCommand::InsertNewline:
2217 ASSERT_NOT_REACHED();
2220 void TypingCommandImpl::markMisspellingsAfterTyping()
2222 // Take a look at the selection that results after typing and determine whether we need to spellcheck.
2223 // Since the word containing the current selection is never marked, this does a check to
2224 // see if typing made a new word that is not in the current selection. Basically, you
2225 // get this by being at the end of a word and typing a space.
2226 Position start(endingSelection().start());
2227 Position p1 = start.previousCharacterPosition().previousWordBoundary();
2228 Position p2 = start.previousWordBoundary();
2230 markMisspellingsInSelection(Selection(p1, start));
2233 void TypingCommandImpl::typingAddedToOpenCommand()
2235 markMisspellingsAfterTyping();
2236 // Do not apply editing to the part on the first time through.
2237 // The part will get told in the same way as all other commands.
2238 // But since this command stays open and is used for additional typing,
2239 // we need to tell the part here as other commands are added.
2240 if (m_applyEditing) {
2241 EditCommand cmd(this);
2242 document()->part()->appliedEditing(cmd);
2244 m_applyEditing = true;
2247 void TypingCommandImpl::insertText(const DOMString &text, bool selectInsertedText)
2249 // FIXME: Improve typing style.
2250 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2251 if (document()->part()->typingStyle() || m_cmds.count() == 0) {
2252 InputTextCommand cmd(document());
2253 applyCommandToComposite(cmd);
2254 cmd.input(text, selectInsertedText);
2257 EditCommand lastCommand = m_cmds.last();
2258 if (lastCommand.isInputTextCommand()) {
2259 static_cast<InputTextCommand &>(lastCommand).input(text, selectInsertedText);
2262 InputTextCommand cmd(document());
2263 applyCommandToComposite(cmd);
2264 cmd.input(text, selectInsertedText);
2267 typingAddedToOpenCommand();
2270 void TypingCommandImpl::insertNewline()
2272 InputNewlineCommand cmd(document());
2273 applyCommandToComposite(cmd);
2274 typingAddedToOpenCommand();
2277 void TypingCommandImpl::issueCommandForDeleteKey()
2279 Selection selectionToDelete;
2281 switch (endingSelection().state()) {
2282 case Selection::RANGE:
2283 selectionToDelete = endingSelection();
2285 case Selection::CARET: {
2286 // Handle delete at beginning-of-block case.
2287 // Do nothing in the case that the caret is at the start of a
2288 // root editable element or at the start of a document.
2289 Position pos(endingSelection().start());
2290 Position start = CaretPosition(pos).previous().deepEquivalent();
2291 Position end = CaretPosition(pos).deepEquivalent();
2292 if (start.notEmpty() && end.notEmpty() && start.node()->rootEditableElement() == end.node()->rootEditableElement())
2293 selectionToDelete = Selection(start, end);
2296 case Selection::NONE:
2297 ASSERT_NOT_REACHED();
2301 if (selectionToDelete.notEmpty()) {
2302 deleteSelection(selectionToDelete);
2303 typingAddedToOpenCommand();
2307 void TypingCommandImpl::deleteKeyPressed()
2309 // EDIT FIXME: The ifdef'ed out code below should be re-enabled.
2310 // In order for this to happen, the deleteCharacter case
2311 // needs work. Specifically, the caret-positioning code
2312 // and whitespace-handling code in DeleteSelectionCommandImpl::doApply()
2313 // needs to be factored out so it can be used again here.
2314 // Until that work is done, issueCommandForDeleteKey() does the
2315 // right thing, but less efficiently and with the cost of more
2317 issueCommandForDeleteKey();
2319 if (m_cmds.count() == 0) {
2320 issueCommandForDeleteKey();
2323 EditCommand lastCommand = m_cmds.last();
2324 if (lastCommand.isInputTextCommand()) {
2325 InputTextCommand &cmd = static_cast<InputTextCommand &>(lastCommand);
2326 cmd.deleteCharacter();
2327 if (cmd.charactersAdded() == 0) {
2328 removeCommand(lastCommand);
2331 else if (lastCommand.isInputNewlineCommand()) {
2332 lastCommand.unapply();
2333 removeCommand(lastCommand);
2336 issueCommandForDeleteKey();
2342 void TypingCommandImpl::removeCommand(const EditCommand &cmd)
2344 // NOTE: If the passed-in command is the last command in the
2345 // composite, we could remove all traces of this typing command
2346 // from the system, including the undo chain. Other editors do
2347 // not do this, but we could.
2350 if (m_cmds.count() == 0)
2351 setEndingSelection(startingSelection());
2353 setEndingSelection(m_cmds.last().endingSelection());
2356 bool TypingCommandImpl::preservesTypingStyle() const
2358 switch (m_commandType) {
2359 case TypingCommand::DeleteKey:
2361 case TypingCommand::InsertText:
2362 case TypingCommand::InsertNewline:
2365 ASSERT_NOT_REACHED();
2369 bool TypingCommandImpl::isTypingCommand() const
2374 //------------------------------------------------------------------------------------------
2376 } // namespace khtml