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 "dom/dom_position.h"
29 #include "html/html_elementimpl.h"
30 #include "html/html_imageimpl.h"
31 #include "htmlattrs.h"
33 #include "khtml_part.h"
34 #include "khtml_selection.h"
35 #include "khtmlview.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_edititerator.h"
42 #include "xml/dom_nodeimpl.h"
43 #include "xml/dom_stringimpl.h"
44 #include "xml/dom_textimpl.h"
45 #include "xml/dom2_rangeimpl.h"
48 #include "KWQAssertions.h"
49 #include "KWQLogging.h"
52 using DOM::DocumentFragmentImpl;
53 using DOM::DocumentImpl;
54 using DOM::DOMPosition;
56 using DOM::DOMStringImpl;
57 using DOM::EditingTextImpl;
58 using DOM::EditIterator;
59 using DOM::ElementImpl;
60 using DOM::HTMLElementImpl;
61 using DOM::HTMLImageElementImpl;
64 using DOM::NodeListImpl;
69 using khtml::AppendNodeCommand;
70 using khtml::AppendNodeCommandImpl;
71 using khtml::CompositeEditCommand;
72 using khtml::CompositeEditCommandImpl;
73 using khtml::DeleteCollapsibleWhitespaceCommand;
74 using khtml::DeleteCollapsibleWhitespaceCommandImpl;
75 using khtml::DeleteSelectionCommand;
76 using khtml::DeleteSelectionCommandImpl;
77 using khtml::DeleteTextCommand;
78 using khtml::DeleteTextCommandImpl;
79 using khtml::EditCommand;
80 using khtml::EditCommandImpl;
81 using khtml::InlineTextBox;
82 using khtml::InputNewlineCommand;
83 using khtml::InputNewlineCommandImpl;
84 using khtml::InputTextCommand;
85 using khtml::InputTextCommandImpl;
86 using khtml::InsertNodeBeforeCommand;
87 using khtml::InsertNodeBeforeCommandImpl;
88 using khtml::InsertTextCommand;
89 using khtml::InsertTextCommandImpl;
90 using khtml::JoinTextNodesCommand;
91 using khtml::JoinTextNodesCommandImpl;
92 using khtml::RemoveNodeCommand;
93 using khtml::RemoveNodeCommandImpl;
94 using khtml::RemoveNodeAndPruneCommand;
95 using khtml::RemoveNodeAndPruneCommandImpl;
96 using khtml::RenderObject;
97 using khtml::RenderStyle;
98 using khtml::RenderText;
99 using khtml::PasteMarkupCommand;
100 using khtml::PasteMarkupCommandImpl;
101 using khtml::SplitTextNodeCommand;
102 using khtml::SplitTextNodeCommandImpl;
103 using khtml::TypingCommand;
104 using khtml::TypingCommandImpl;
107 #define ASSERT(assertion) ((void)0)
108 #define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
109 #define ASSERT_NOT_REACHED() ((void)0)
110 #define LOG(channel, formatAndArgs...) ((void)0)
111 #define ERROR(formatAndArgs...) ((void)0)
113 #define debugPosition(a,b) ((void)0)
117 static inline bool isNBSP(const QChar &c)
119 return c == QChar(0xa0);
122 static inline bool isWS(const QChar &c)
124 return c.isSpace() && c != QChar(0xa0);
127 static inline bool isWS(const DOMString &text)
129 if (text.length() != 1)
132 return isWS(text[0]);
135 static inline bool isWS(const DOMPosition &pos)
140 if (!pos.node()->isTextNode())
143 const DOMString &string = static_cast<TextImpl *>(pos.node())->data();
144 return isWS(string[pos.offset()]);
147 static bool shouldPruneNode(NodeImpl *node)
152 RenderObject *renderer = node->renderer();
156 if (node->hasChildNodes())
159 if (renderer->isBR() || renderer->isBlockFlow() || renderer->isReplaced())
162 if (node->isTextNode()) {
163 TextImpl *text = static_cast<TextImpl *>(node);
164 if (text->length() == 0)
169 if (!node->isHTMLElement() && !node->isXMLElementNode())
172 if (node->id() == ID_BODY)
175 if (!node->isContentEditable())
181 static DOMPosition leadingWhitespacePosition(const DOMPosition &pos)
183 ASSERT(pos.notEmpty());
185 KHTMLSelection selection(pos);
186 DOMPosition prev = pos.previousCharacterPosition();
187 if (prev != pos && prev.node()->inSameContainingEditableBlock(pos.node()) && prev.node()->isTextNode()) {
188 DOMString string = static_cast<TextImpl *>(prev.node())->data();
189 if (isWS(string[prev.offset()]))
193 return DOMPosition();
196 static DOMPosition trailingWhitespacePosition(const DOMPosition &pos)
198 ASSERT(pos.notEmpty());
200 if (pos.node()->isTextNode()) {
201 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
202 if (pos.offset() >= (long)textNode->length()) {
203 DOMPosition next = pos.nextCharacterPosition();
204 if (next != pos && next.node()->inSameContainingEditableBlock(pos.node()) && next.node()->isTextNode()) {
205 DOMString string = static_cast<TextImpl *>(next.node())->data();
211 DOMString string = static_cast<TextImpl *>(pos.node())->data();
212 if (isWS(string[pos.offset()]))
217 return DOMPosition();
220 static bool textNodesAreJoinable(TextImpl *text1, TextImpl *text2)
225 return (text1->nextSibling() == text2);
228 static DOMString &nonBreakingSpaceString()
230 static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
231 return nonBreakingSpaceString;
234 static void debugPosition(const char *prefix, const DOMPosition &pos)
236 LOG(Editing, "%s%s %p : %d", prefix, getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
239 //------------------------------------------------------------------------------------------
242 EditCommandImpl::EditCommandImpl(DocumentImpl *document)
243 : SharedCommandImpl(), m_document(document), m_state(NotApplied), m_parent(0)
246 ASSERT(m_document->part());
248 m_startingSelection = m_document->part()->selection();
249 m_endingSelection = m_startingSelection;
252 EditCommandImpl::~EditCommandImpl()
258 int EditCommandImpl::commandID() const
260 return EditCommandID;
263 void EditCommandImpl::apply()
266 ASSERT(m_document->part());
267 ASSERT(state() == NotApplied);
273 if (!isCompositeStep()) {
274 EditCommand cmd(this);
275 m_document->part()->appliedEditing(cmd);
279 void EditCommandImpl::unapply()
282 ASSERT(m_document->part());
283 ASSERT(state() == Applied);
287 m_state = NotApplied;
289 if (!isCompositeStep()) {
290 EditCommand cmd(this);
291 m_document->part()->unappliedEditing(cmd);
295 void EditCommandImpl::reapply()
298 ASSERT(m_document->part());
299 ASSERT(state() == NotApplied);
305 if (!isCompositeStep()) {
306 EditCommand cmd(this);
307 m_document->part()->reappliedEditing(cmd);
311 void EditCommandImpl::doReapply()
316 void EditCommandImpl::setStartingSelection(const KHTMLSelection &s)
318 m_startingSelection = s;
319 EditCommand cmd = parent();
320 while (cmd.notNull()) {
321 cmd.setStartingSelection(s);
324 moveToStartingSelection();
327 void EditCommandImpl::setEndingSelection(const KHTMLSelection &s)
329 m_endingSelection = s;
330 EditCommand cmd = parent();
331 while (cmd.notNull()) {
332 cmd.setEndingSelection(s);
335 moveToEndingSelection();
338 void EditCommandImpl::moveToStartingSelection()
341 ASSERT(m_document->part());
342 m_document->part()->takeSelectionFrom(this, false);
345 void EditCommandImpl::moveToEndingSelection()
348 ASSERT(m_document->part());
349 m_document->part()->takeSelectionFrom(this, true);
352 EditCommand EditCommandImpl::parent() const
357 void EditCommandImpl::setParent(const EditCommand &cmd)
362 //------------------------------------------------------------------------------------------
363 // CompositeEditCommandImpl
365 CompositeEditCommandImpl::CompositeEditCommandImpl(DocumentImpl *document)
366 : EditCommandImpl(document)
370 CompositeEditCommandImpl::~CompositeEditCommandImpl()
374 int CompositeEditCommandImpl::commandID() const
376 return CompositeEditCommandID;
379 void CompositeEditCommandImpl::doUnapply()
381 if (m_cmds.count() == 0) {
385 for (int i = m_cmds.count() - 1; i >= 0; --i)
386 m_cmds[i]->unapply();
388 moveToStartingSelection();
389 setState(NotApplied);
392 void CompositeEditCommandImpl::doReapply()
394 if (m_cmds.count() == 0) {
398 for (QValueList<EditCommand>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
401 moveToEndingSelection();
406 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
408 void CompositeEditCommandImpl::applyCommandToComposite(EditCommand &cmd)
410 cmd.setStartingSelection(endingSelection());
411 cmd.setEndingSelection(endingSelection());
417 void CompositeEditCommandImpl::insertNodeBefore(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild)
419 InsertNodeBeforeCommand cmd(document(), insertChild, refChild);
420 applyCommandToComposite(cmd);
423 void CompositeEditCommandImpl::insertNodeAfter(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild)
425 if (refChild->parentNode()->lastChild() == refChild) {
426 appendNode(refChild->parentNode(), insertChild);
429 ASSERT(refChild->nextSibling());
430 insertNodeBefore(insertChild, refChild->nextSibling());
434 void CompositeEditCommandImpl::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
436 if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
437 NodeImpl *child = refChild->firstChild();
438 for (long i = 0; child && i < offset; i++)
439 child = child->nextSibling();
441 insertNodeBefore(insertChild, child);
443 appendNode(refChild, insertChild);
445 else if (refChild->caretMinOffset() >= offset) {
446 insertNodeBefore(insertChild, refChild);
448 else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
449 splitTextNode(static_cast<TextImpl *>(refChild), offset);
450 insertNodeBefore(insertChild, refChild);
453 insertNodeAfter(insertChild, refChild);
457 void CompositeEditCommandImpl::appendNode(DOM::NodeImpl *parent, DOM::NodeImpl *appendChild)
459 AppendNodeCommand cmd(document(), parent, appendChild);
460 applyCommandToComposite(cmd);
463 void CompositeEditCommandImpl::removeNode(DOM::NodeImpl *removeChild)
465 RemoveNodeCommand cmd(document(), removeChild);
466 applyCommandToComposite(cmd);
469 void CompositeEditCommandImpl::removeNodeAndPrune(DOM::NodeImpl *removeChild)
471 RemoveNodeAndPruneCommand cmd(document(), removeChild);
472 applyCommandToComposite(cmd);
475 void CompositeEditCommandImpl::splitTextNode(DOM::TextImpl *text, long offset)
477 SplitTextNodeCommand cmd(document(), text, offset);
478 applyCommandToComposite(cmd);
481 void CompositeEditCommandImpl::joinTextNodes(DOM::TextImpl *text1, DOM::TextImpl *text2)
483 JoinTextNodesCommand cmd(document(), text1, text2);
484 applyCommandToComposite(cmd);
487 void CompositeEditCommandImpl::inputText(const DOMString &text)
489 InputTextCommand cmd(document());
490 applyCommandToComposite(cmd);
494 void CompositeEditCommandImpl::insertText(DOM::TextImpl *node, long offset, const DOM::DOMString &text)
496 InsertTextCommand cmd(document(), node, offset, text);
497 applyCommandToComposite(cmd);
500 void CompositeEditCommandImpl::deleteText(DOM::TextImpl *node, long offset, long count)
502 DeleteTextCommand cmd(document(), node, offset, count);
503 applyCommandToComposite(cmd);
506 void CompositeEditCommandImpl::replaceText(DOM::TextImpl *node, long offset, long count, const DOM::DOMString &replacementText)
508 DeleteTextCommand deleteCommand(document(), node, offset, count);
509 applyCommandToComposite(deleteCommand);
510 InsertTextCommand insertCommand(document(), node, offset, replacementText);
511 applyCommandToComposite(insertCommand);
514 void CompositeEditCommandImpl::deleteSelection()
516 if (endingSelection().state() == KHTMLSelection::RANGE) {
517 DeleteSelectionCommand cmd(document());
518 applyCommandToComposite(cmd);
522 void CompositeEditCommandImpl::deleteSelection(const KHTMLSelection &selection)
524 if (selection.state() == KHTMLSelection::RANGE) {
525 DeleteSelectionCommand cmd(document(), selection);
526 applyCommandToComposite(cmd);
530 void CompositeEditCommandImpl::deleteCollapsibleWhitespace()
532 DeleteCollapsibleWhitespaceCommand cmd(document());
533 applyCommandToComposite(cmd);
536 void CompositeEditCommandImpl::deleteCollapsibleWhitespace(const KHTMLSelection &selection)
538 DeleteCollapsibleWhitespaceCommand cmd(document(), selection);
539 applyCommandToComposite(cmd);
542 //==========================================================================================
544 //------------------------------------------------------------------------------------------
545 // AppendNodeCommandImpl
547 AppendNodeCommandImpl::AppendNodeCommandImpl(DocumentImpl *document, NodeImpl *parentNode, NodeImpl *appendChild)
548 : EditCommandImpl(document), m_parentNode(parentNode), m_appendChild(appendChild)
550 ASSERT(m_parentNode);
553 ASSERT(m_appendChild);
554 m_appendChild->ref();
557 AppendNodeCommandImpl::~AppendNodeCommandImpl()
560 m_parentNode->deref();
562 m_appendChild->deref();
565 int AppendNodeCommandImpl::commandID() const
567 return AppendNodeCommandID;
570 void AppendNodeCommandImpl::doApply()
572 ASSERT(m_parentNode);
573 ASSERT(m_appendChild);
575 int exceptionCode = 0;
576 m_parentNode->appendChild(m_appendChild, exceptionCode);
577 ASSERT(exceptionCode == 0);
580 void AppendNodeCommandImpl::doUnapply()
582 ASSERT(m_parentNode);
583 ASSERT(m_appendChild);
584 ASSERT(state() == Applied);
586 int exceptionCode = 0;
587 m_parentNode->removeChild(m_appendChild, exceptionCode);
588 ASSERT(exceptionCode == 0);
591 //------------------------------------------------------------------------------------------
592 // DeleteCollapsibleWhitespaceCommandImpl
594 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document)
595 : CompositeEditCommandImpl(document), m_selectionToCollapse(endingSelection()), m_charactersDeleted(0)
599 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document, const KHTMLSelection &selection)
600 : CompositeEditCommandImpl(document), m_selectionToCollapse(selection), m_charactersDeleted(0)
604 DeleteCollapsibleWhitespaceCommandImpl::~DeleteCollapsibleWhitespaceCommandImpl()
608 int DeleteCollapsibleWhitespaceCommandImpl::commandID() const
610 return DeleteCollapsibleWhitespaceCommandID;
613 static bool shouldDeleteUpstreamPosition(const DOMPosition &pos)
615 if (!pos.node()->isTextNode())
618 RenderObject *renderer = pos.node()->renderer();
622 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
623 if (pos.offset() >= (long)textNode->length())
626 if (pos.isLastRenderedPositionInEditableBlock())
629 if (pos.isFirstRenderedPositionOnLine() || pos.isLastRenderedPositionOnLine())
632 RenderText *textRenderer = static_cast<RenderText *>(renderer);
634 for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
635 if (pos.offset() < box->m_start) {
638 if (pos.offset() >= box->m_start && pos.offset() < box->m_start + box->m_len)
645 DOMPosition DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace(const DOMPosition &pos)
647 DOMPosition upstream = pos.equivalentUpstreamPosition();
648 DOMPosition downstream = pos.equivalentDownstreamPosition();
650 bool del = shouldDeleteUpstreamPosition(upstream);
652 LOG(Editing, "pos: %s [%p:%d]\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
653 if (upstream == downstream) {
654 LOG(Editing, "same: %s [%p:%d]\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
657 LOG(Editing, "upstream: %s %s [%p:%d]\n", del ? "DELETE" : "SKIP", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
658 EditIterator it(upstream);
659 for (it.next(); it.current() != downstream; it.next()) {
660 if (it.current().node()->isTextNode() && (long)static_cast<TextImpl *>(it.current().node())->length() == it.current().offset())
661 LOG(Editing, " node: AT END %s [%p:%d]\n", getTagName(it.current().node()->id()).string().latin1(), it.current().node(), it.current().offset());
663 LOG(Editing, " node: DELETE %s [%p:%d]\n", getTagName(it.current().node()->id()).string().latin1(), it.current().node(), it.current().offset());
665 LOG(Editing, "downstream: %s [%p:%d]\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
668 if (upstream == downstream)
671 EditIterator it(upstream);
672 DOMPosition deleteStart = upstream;
674 deleteStart = it.peekNext();
675 if (deleteStart == downstream)
679 DOMPosition endingPosition = upstream;
681 while (it.current() != downstream) {
683 DOMPosition next = it.peekNext();
684 if (next.node() != deleteStart.node()) {
685 ASSERT(deleteStart.node()->isTextNode());
686 TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node());
687 unsigned long count = it.current().offset() - deleteStart.offset();
688 if (count == textNode->length()) {
689 LOG(Editing, " removeNodeAndPrune 1: [%p]\n", textNode);
690 if (textNode == endingPosition.node())
691 endingPosition = DOMPosition(next.node(), next.node()->caretMinOffset());
692 removeNodeAndPrune(textNode);
695 LOG(Editing, " deleteText 1: [%p:%d:%d:%d]\n", textNode, textNode->length(), deleteStart.offset(), it.current().offset() - deleteStart.offset());
696 deleteText(textNode, deleteStart.offset(), count);
700 else if (next == downstream) {
701 ASSERT(deleteStart.node() == downstream.node());
702 ASSERT(downstream.node()->isTextNode());
703 TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node());
704 unsigned long count = downstream.offset() - deleteStart.offset();
705 ASSERT(count <= textNode->length());
706 if (count == textNode->length()) {
707 LOG(Editing, " removeNodeAndPrune 2: [%p]\n", textNode);
708 removeNodeAndPrune(textNode);
711 LOG(Editing, " deleteText 2: [%p:%d:%d:%d]\n", textNode, textNode->length(), deleteStart.offset(), count);
712 deleteText(textNode, deleteStart.offset(), count);
713 m_charactersDeleted = count;
714 endingPosition = DOMPosition(downstream.node(), downstream.offset() - m_charactersDeleted);
718 it.setPosition(next);
721 return endingPosition;
724 void DeleteCollapsibleWhitespaceCommandImpl::doApply()
726 int state = m_selectionToCollapse.state();
727 if (state == KHTMLSelection::CARET) {
728 DOMPosition endPosition = deleteWhitespace(m_selectionToCollapse.startPosition());
729 setEndingSelection(endPosition);
730 LOG(Editing, "-----------------------------------------------------\n");
732 else if (state == KHTMLSelection::RANGE) {
733 DOMPosition startPosition = deleteWhitespace(m_selectionToCollapse.startPosition());
734 LOG(Editing, "-----------------------------------------------------\n");
735 DOMPosition endPosition = m_selectionToCollapse.endPosition();
736 if (m_charactersDeleted > 0 && startPosition.node() == endPosition.node()) {
737 LOG(Editing, "adjust end position by %d\n", m_charactersDeleted);
738 endPosition = DOMPosition(endPosition.node(), endPosition.offset() - m_charactersDeleted);
740 endPosition = deleteWhitespace(endPosition);
741 setEndingSelection(KHTMLSelection(startPosition, endPosition));
742 LOG(Editing, "=====================================================\n");
746 //------------------------------------------------------------------------------------------
747 // DeleteSelectionCommandImpl
749 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DOM::DocumentImpl *document)
750 : CompositeEditCommandImpl(document)
752 m_selectionToDelete = endingSelection();
755 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DOM::DocumentImpl *document, const KHTMLSelection &selection)
756 : CompositeEditCommandImpl(document)
758 m_selectionToDelete = selection;
761 DeleteSelectionCommandImpl::~DeleteSelectionCommandImpl()
765 int DeleteSelectionCommandImpl::commandID() const
767 return DeleteSelectionCommandID;
770 void DeleteSelectionCommandImpl::joinTextNodesWithSameStyle()
772 KHTMLSelection selection = endingSelection();
774 if (selection.state() != KHTMLSelection::CARET)
777 DOMPosition pos = selection.startPosition();
779 if (!pos.node()->isTextNode())
782 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
784 if (pos.offset() == 0) {
785 EditIterator it(pos);
786 DOMPosition prev = it.previous();
789 if (prev.node()->isTextNode()) {
790 TextImpl *prevTextNode = static_cast<TextImpl *>(prev.node());
791 if (textNodesAreJoinable(prevTextNode, textNode)) {
792 joinTextNodes(prevTextNode, textNode);
793 setEndingSelection(DOMPosition(textNode, prevTextNode->length()));
794 LOG(Editing, "joinTextNodesWithSameStyle [1]\n");
798 else if (pos.offset() == (long)textNode->length()) {
799 EditIterator it(pos);
800 DOMPosition next = it.next();
803 if (next.node()->isTextNode()) {
804 TextImpl *nextTextNode = static_cast<TextImpl *>(next.node());
805 if (textNodesAreJoinable(textNode, nextTextNode)) {
806 joinTextNodes(textNode, nextTextNode);
807 setEndingSelection(DOMPosition(nextTextNode, pos.offset()));
808 LOG(Editing, "joinTextNodesWithSameStyle [2]\n");
814 bool DeleteSelectionCommandImpl::containsOnlyWhitespace(const DOMPosition &start, const DOMPosition &end)
816 // Returns whether the range contains only whitespace characters.
817 // This is inclusive of the start, but not of the end.
818 EditIterator it(start);
819 while (!it.atEnd()) {
820 if (!it.current().node()->isTextNode())
822 const DOMString &text = static_cast<TextImpl *>(it.current().node())->data();
823 // EDIT FIXME: signed/unsigned mismatch
824 if (text.length() > INT_MAX)
826 if (it.current().offset() < (int)text.length() && !isWS(text[it.current().offset()]))
829 if (it.current() == end)
835 void DeleteSelectionCommandImpl::doApply()
837 if (m_selectionToDelete.state() != KHTMLSelection::RANGE)
840 deleteCollapsibleWhitespace(m_selectionToDelete);
841 KHTMLSelection selection = endingSelection();
843 DOMPosition endingPosition;
844 bool adjustEndingPositionDownstream = false;
846 DOMPosition upstreamStart = selection.startPosition().equivalentUpstreamPosition();
847 DOMPosition downstreamStart = selection.startPosition().equivalentDownstreamPosition();
848 DOMPosition upstreamEnd = selection.endPosition().equivalentUpstreamPosition();
849 DOMPosition downstreamEnd = selection.endPosition().equivalentDownstreamPosition();
851 bool onlyWhitespace = containsOnlyWhitespace(upstreamStart, downstreamEnd);
853 bool startCompletelySelected = !onlyWhitespace &&
854 (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset() &&
855 ((downstreamStart.node() != upstreamEnd.node()) ||
856 (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset())));
858 bool endCompletelySelected = !onlyWhitespace &&
859 (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset() &&
860 ((downstreamStart.node() != upstreamEnd.node()) ||
861 (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset())));
863 unsigned long startRenderedOffset = downstreamStart.renderedOffset();
865 bool startAtStartOfRootEditableBlock = startRenderedOffset == 0 && downstreamStart.inFirstEditableInRootEditableBlock();
866 bool startAtStartOfBlock = startAtStartOfRootEditableBlock ||
867 (startRenderedOffset == 0 && downstreamStart.inFirstEditableInContainingEditableBlock());
868 bool endAtEndOfBlock = downstreamEnd.isLastRenderedPositionInEditableBlock();
870 debugPosition("upstreamStart: ", upstreamStart);
871 debugPosition("downstreamStart: ", downstreamStart);
872 debugPosition("upstreamEnd: ", upstreamEnd);
873 debugPosition("downstreamEnd: ", downstreamEnd);
874 LOG(Editing, "start selected: %s", startCompletelySelected ? "YES" : "NO");
875 LOG(Editing, "at start block: %s", startAtStartOfBlock ? "YES" : "NO");
876 LOG(Editing, "at start root block: %s", startAtStartOfRootEditableBlock ? "YES" : "NO");
877 LOG(Editing, "at end block: %s", endAtEndOfBlock ? "YES" : "NO");
878 LOG(Editing, "only whitespace: %s", onlyWhitespace ? "YES" : "NO");
880 // Start is not completely selected
881 if (startAtStartOfBlock) {
882 LOG(Editing, "ending position case 1");
883 endingPosition = DOMPosition(downstreamStart.node()->containingEditableBlock(), 1);
884 adjustEndingPositionDownstream = true;
886 else if (!startCompletelySelected) {
887 LOG(Editing, "ending position case 2");
888 endingPosition = upstreamStart;
889 if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1)
890 adjustEndingPositionDownstream = true;
892 else if (upstreamStart != downstreamStart) {
893 LOG(Editing, "ending position case 3");
894 endingPosition = upstreamStart;
895 if (downstreamStart.node()->id() == ID_BR && downstreamStart.offset() == 0)
896 adjustEndingPositionDownstream = true;
897 if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1)
898 adjustEndingPositionDownstream = true;
902 // Figure out the whitespace conversions to do
904 if ((startAtStartOfBlock && !endAtEndOfBlock) || (!startCompletelySelected && adjustEndingPositionDownstream)) {
905 // convert trailing whitespace
906 DOMPosition trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
907 if (trailing.notEmpty()) {
908 debugPosition("convertTrailingWhitespace: ", trailing);
909 DOMPosition collapse = trailing.nextCharacterPosition();
910 if (collapse != trailing)
911 deleteCollapsibleWhitespace(collapse);
912 TextImpl *textNode = static_cast<TextImpl *>(trailing.node());
913 replaceText(textNode, trailing.offset(), 1, nonBreakingSpaceString());
916 else if (!startAtStartOfBlock && endAtEndOfBlock) {
917 // convert leading whitespace
918 DOMPosition leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
919 if (leading.notEmpty()) {
920 debugPosition("convertLeadingWhitespace: ", leading);
921 TextImpl *textNode = static_cast<TextImpl *>(leading.node());
922 replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
925 else if (!startAtStartOfBlock && !endAtEndOfBlock) {
926 // convert contiguous whitespace
927 DOMPosition leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
928 DOMPosition trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
929 if (leading.notEmpty() && trailing.notEmpty()) {
930 debugPosition("convertLeadingWhitespace [contiguous]: ", leading);
931 TextImpl *textNode = static_cast<TextImpl *>(leading.node());
932 replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
939 NodeImpl *n = downstreamStart.node()->traverseNextNode();
941 // work on start node
942 if (startCompletelySelected) {
943 LOG(Editing, "start node delete case 1");
944 removeNodeAndPrune(downstreamStart.node());
946 else if (onlyWhitespace) {
947 // Selection only contains whitespace. This is really a special-case to
948 // handle significant whitespace that is collapsed at the end of a line,
949 // but also handles deleting a space in mid-line.
950 LOG(Editing, "start node delete case 2");
951 ASSERT(upstreamStart.node()->isTextNode());
952 TextImpl *text = static_cast<TextImpl *>(upstreamStart.node());
953 int length = downstreamStart.node() == upstreamStart.node() ?
954 kMax(downstreamStart.offset() - upstreamStart.offset(), 1L) :
955 text->length() - upstreamStart.offset();
956 deleteText(text, upstreamStart.offset(), length);
958 else if (downstreamStart.node()->isTextNode()) {
959 LOG(Editing, "start node delete case 3");
960 TextImpl *text = static_cast<TextImpl *>(downstreamStart.node());
961 int endOffset = text == upstreamEnd.node() ? upstreamEnd.offset() : text->length();
962 if (endOffset > downstreamStart.offset()) {
963 deleteText(text, downstreamStart.offset(), endOffset - downstreamStart.offset());
967 // we have clipped the end of a non-text element
968 // the offset must be 1 here. if it is, do nothing and move on.
969 LOG(Editing, "start node delete case 4");
970 ASSERT(downstreamStart.offset() == 1);
973 if (!onlyWhitespace && downstreamStart.node() != upstreamEnd.node()) {
974 // work on intermediate nodes
975 while (n != upstreamEnd.node()) {
977 n = n->traverseNextNode();
978 if (d->renderer() && d->renderer()->isEditable())
979 removeNodeAndPrune(d);
983 ASSERT(n == upstreamEnd.node());
984 if (endCompletelySelected) {
985 removeNodeAndPrune(upstreamEnd.node());
987 else if (upstreamEnd.node()->isTextNode()) {
988 if (upstreamEnd.offset() > 0) {
989 TextImpl *text = static_cast<TextImpl *>(upstreamEnd.node());
990 deleteText(text, 0, upstreamEnd.offset());
994 // we have clipped the beginning of a non-text element
995 // the offset must be 0 here. if it is, do nothing and move on.
996 ASSERT(downstreamStart.offset() == 0);
1000 if (adjustEndingPositionDownstream) {
1001 LOG(Editing, "adjust ending position downstream");
1002 endingPosition = endingPosition.equivalentDownstreamPosition();
1005 debugPosition("ending position: ", endingPosition);
1006 setEndingSelection(endingPosition);
1008 LOG(Editing, "-----------------------------------------------------\n");
1011 //------------------------------------------------------------------------------------------
1012 // DeleteTextCommandImpl
1014 DeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, long count)
1015 : EditCommandImpl(document), m_node(node), m_offset(offset), m_count(count)
1018 ASSERT(m_offset >= 0);
1019 ASSERT(m_count >= 0);
1024 DeleteTextCommandImpl::~DeleteTextCommandImpl()
1030 int DeleteTextCommandImpl::commandID() const
1032 return DeleteTextCommandID;
1035 void DeleteTextCommandImpl::doApply()
1039 int exceptionCode = 0;
1040 m_text = m_node->substringData(m_offset, m_count, exceptionCode);
1041 ASSERT(exceptionCode == 0);
1043 m_node->deleteData(m_offset, m_count, exceptionCode);
1044 ASSERT(exceptionCode == 0);
1047 void DeleteTextCommandImpl::doUnapply()
1050 ASSERT(!m_text.isEmpty());
1052 int exceptionCode = 0;
1053 m_node->insertData(m_offset, m_text, exceptionCode);
1054 ASSERT(exceptionCode == 0);
1057 //------------------------------------------------------------------------------------------
1058 // InputNewlineCommandImpl
1060 InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document)
1061 : CompositeEditCommandImpl(document)
1065 InputNewlineCommandImpl::~InputNewlineCommandImpl()
1069 int InputNewlineCommandImpl::commandID() const
1071 return InputNewlineCommandID;
1074 void InputNewlineCommandImpl::doApply()
1077 KHTMLSelection selection = endingSelection();
1079 int exceptionCode = 0;
1080 ElementImpl *breakNode = document()->createHTMLElement("BR", exceptionCode);
1081 ASSERT(exceptionCode == 0);
1083 DOMPosition pos = selection.startPosition().equivalentDownstreamPosition();
1084 bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
1085 bool atStart = pos.offset() <= pos.node()->caretMinOffset();
1086 bool atEndOfBlock = pos.isLastRenderedPositionInEditableBlock();
1089 LOG(Editing, "input newline case 1");
1090 appendNode(pos.node()->containingEditableBlock(), breakNode);
1091 // EDIT FIXME: This should not insert a non-breaking space after the BR.
1092 // But for right now, it gets the BR to render.
1093 TextImpl *editingTextNode = document()->createEditingTextNode(nonBreakingSpaceString());
1094 insertNodeAfter(editingTextNode, breakNode);
1095 setEndingSelection(DOMPosition(editingTextNode, 1));
1096 editingTextNode->deref();
1099 LOG(Editing, "input newline case 2");
1100 insertNodeAfter(breakNode, pos.node());
1101 setEndingSelection(DOMPosition(breakNode, 0));
1104 LOG(Editing, "input newline case 3");
1105 insertNodeAt(breakNode, pos.node(), 0);
1106 setEndingSelection(DOMPosition(pos.node(), 0));
1109 LOG(Editing, "input newline case 4");
1110 ASSERT(pos.node()->isTextNode());
1111 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1112 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.startOffset(), exceptionCode));
1113 deleteText(textNode, 0, selection.startOffset());
1114 insertNodeBefore(textBeforeNode, textNode);
1115 insertNodeBefore(breakNode, textNode);
1116 textBeforeNode->deref();
1117 setEndingSelection(DOMPosition(textNode, 0));
1123 //------------------------------------------------------------------------------------------
1124 // InputTextCommandImpl
1126 InputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document)
1127 : CompositeEditCommandImpl(document), m_insertedTextNode(0), m_charactersAdded(0)
1131 InputTextCommandImpl::~InputTextCommandImpl()
1133 if (m_insertedTextNode)
1134 m_insertedTextNode->deref();
1137 int InputTextCommandImpl::commandID() const
1139 return InputTextCommandID;
1142 void InputTextCommandImpl::doApply()
1146 void InputTextCommandImpl::input(const DOMString &text)
1151 void InputTextCommandImpl::deleteCharacter()
1153 ASSERT(state() == Applied);
1155 KHTMLSelection selection = endingSelection();
1157 if (!selection.startNode()->isTextNode())
1160 int exceptionCode = 0;
1161 int offset = selection.startOffset() - 1;
1162 if (offset >= selection.startNode()->caretMinOffset()) {
1163 TextImpl *textNode = static_cast<TextImpl *>(selection.startNode());
1164 textNode->deleteData(offset, 1, exceptionCode);
1165 ASSERT(exceptionCode == 0);
1166 selection = KHTMLSelection(textNode, offset);
1167 setEndingSelection(selection);
1168 m_charactersAdded--;
1172 DOMPosition InputTextCommandImpl::prepareForTextInsertion(bool adjustDownstream)
1174 // Prepare for text input by looking at the current position.
1175 // It may be necessary to insert a text node to receive characters.
1176 KHTMLSelection selection = endingSelection();
1177 ASSERT(selection.state() == KHTMLSelection::CARET);
1179 DOMPosition pos = selection.startPosition();
1180 if (adjustDownstream)
1181 pos = pos.equivalentDownstreamPosition();
1183 pos = pos.equivalentUpstreamPosition();
1185 if (!pos.node()->isTextNode()) {
1186 if (!m_insertedTextNode) {
1187 m_insertedTextNode = document()->createEditingTextNode("");
1188 m_insertedTextNode->ref();
1191 if (pos.node()->isEditableBlock()) {
1192 LOG(Editing, "prepareForTextInsertion case 1");
1193 appendNode(pos.node(), m_insertedTextNode);
1195 else if (pos.node()->id() == ID_BR && pos.offset() == 1) {
1196 LOG(Editing, "prepareForTextInsertion case 2");
1197 insertNodeBefore(m_insertedTextNode, pos.node());
1199 else if (pos.node()->caretMinOffset() == pos.offset()) {
1200 LOG(Editing, "prepareForTextInsertion case 3");
1201 insertNodeBefore(m_insertedTextNode, pos.node());
1203 else if (pos.node()->caretMaxOffset() == pos.offset()) {
1204 LOG(Editing, "prepareForTextInsertion case 4");
1205 insertNodeAfter(m_insertedTextNode, pos.node());
1208 ASSERT_NOT_REACHED();
1210 pos = DOMPosition(m_insertedTextNode, 0);
1216 void InputTextCommandImpl::execute(const DOMString &text)
1218 KHTMLSelection selection = endingSelection();
1219 bool adjustDownstream = selection.startPosition().isFirstRenderedPositionOnLine();
1221 // Delete the current selection, or collapse whitespace, as needed
1222 if (selection.state() == KHTMLSelection::RANGE)
1225 deleteCollapsibleWhitespace();
1227 // EDIT FIXME: Need to take typing style from upstream text, if any.
1229 // Make sure the document is set up to receive text
1230 DOMPosition pos = prepareForTextInsertion(adjustDownstream);
1232 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1233 long offset = pos.offset();
1235 // This is a temporary implementation for inserting adjoining spaces
1236 // into a document. We are working on a CSS-related whitespace solution
1237 // that will replace this some day.
1239 insertSpace(textNode, offset);
1241 const DOMString &existingText = textNode->data();
1242 if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isWS(existingText[offset - 2])) {
1243 // DOM looks like this:
1244 // character nbsp caret
1245 // As we are about to insert a non-whitespace character at the caret
1246 // convert the nbsp to a regular space.
1247 // EDIT FIXME: This needs to be improved some day to convert back only
1248 // those nbsp's added by the editor to make rendering come out right.
1249 replaceText(textNode, offset - 1, 1, " ");
1251 insertText(textNode, offset, text);
1253 setEndingSelection(DOMPosition(textNode, offset + text.length()));
1254 m_charactersAdded += text.length();
1257 void InputTextCommandImpl::insertSpace(TextImpl *textNode, unsigned long offset)
1261 DOMString text(textNode->data());
1263 // count up all spaces and newlines in front of the caret
1264 // delete all collapsed ones
1265 // this will work out OK since the offset we have been passed has been upstream-ized
1267 for (unsigned int i = offset; i < text.length(); i++) {
1274 // By checking the character at the downstream position, we can
1275 // check if there is a rendered WS at the caret
1276 DOMPosition pos(textNode, offset);
1277 DOMPosition downstream = pos.equivalentDownstreamPosition();
1278 if (downstream.offset() < (long)text.length() && isWS(text[downstream.offset()]))
1279 count--; // leave this WS in
1281 deleteText(textNode, offset, count);
1284 if (offset > 0 && offset <= text.length() - 1 && !isWS(text[offset]) && !isWS(text[offset - 1])) {
1285 // insert a "regular" space
1286 insertText(textNode, offset, " ");
1290 if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
1291 // DOM looks like this:
1293 // insert a space between the two nbsps
1294 insertText(textNode, offset - 1, " ");
1299 insertText(textNode, offset, nonBreakingSpaceString());
1302 //------------------------------------------------------------------------------------------
1303 // InsertNodeBeforeCommandImpl
1305 InsertNodeBeforeCommandImpl::InsertNodeBeforeCommandImpl(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
1306 : EditCommandImpl(document), m_insertChild(insertChild), m_refChild(refChild)
1308 ASSERT(m_insertChild);
1309 m_insertChild->ref();
1315 InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl()
1318 m_insertChild->deref();
1320 m_refChild->deref();
1323 int InsertNodeBeforeCommandImpl::commandID() const
1325 return InsertNodeBeforeCommandID;
1328 void InsertNodeBeforeCommandImpl::doApply()
1330 ASSERT(m_insertChild);
1332 ASSERT(m_refChild->parent());
1334 int exceptionCode = 0;
1335 m_refChild->parent()->insertBefore(m_insertChild, m_refChild, exceptionCode);
1336 ASSERT(exceptionCode == 0);
1339 void InsertNodeBeforeCommandImpl::doUnapply()
1341 ASSERT(m_insertChild);
1343 ASSERT(m_refChild->parent());
1345 int exceptionCode = 0;
1346 m_refChild->parent()->removeChild(m_insertChild, exceptionCode);
1347 ASSERT(exceptionCode == 0);
1350 //------------------------------------------------------------------------------------------
1351 // InsertTextCommandImpl
1353 InsertTextCommandImpl::InsertTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
1354 : EditCommandImpl(document), m_node(node), m_offset(offset)
1357 ASSERT(m_offset >= 0);
1358 ASSERT(text.length() > 0);
1361 m_text = text.copy(); // make a copy to ensure that the string never changes
1364 InsertTextCommandImpl::~InsertTextCommandImpl()
1370 int InsertTextCommandImpl::commandID() const
1372 return InsertTextCommandID;
1375 void InsertTextCommandImpl::doApply()
1378 ASSERT(!m_text.isEmpty());
1380 int exceptionCode = 0;
1381 m_node->insertData(m_offset, m_text, exceptionCode);
1382 ASSERT(exceptionCode == 0);
1385 void InsertTextCommandImpl::doUnapply()
1388 ASSERT(!m_text.isEmpty());
1390 int exceptionCode = 0;
1391 m_node->deleteData(m_offset, m_text.length(), exceptionCode);
1392 ASSERT(exceptionCode == 0);
1395 //------------------------------------------------------------------------------------------
1396 // JoinTextNodesCommandImpl
1398 JoinTextNodesCommandImpl::JoinTextNodesCommandImpl(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
1399 : EditCommandImpl(document), m_text1(text1), m_text2(text2)
1403 ASSERT(m_text1->nextSibling() == m_text2);
1404 ASSERT(m_text1->length() > 0);
1405 ASSERT(m_text2->length() > 0);
1411 JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl()
1419 int JoinTextNodesCommandImpl::commandID() const
1421 return JoinTextNodesCommandID;
1424 void JoinTextNodesCommandImpl::doApply()
1428 ASSERT(m_text1->nextSibling() == m_text2);
1430 int exceptionCode = 0;
1431 m_text2->insertData(0, m_text1->data(), exceptionCode);
1432 ASSERT(exceptionCode == 0);
1434 m_text2->parent()->removeChild(m_text1, exceptionCode);
1435 ASSERT(exceptionCode == 0);
1437 m_offset = m_text1->length();
1440 void JoinTextNodesCommandImpl::doUnapply()
1443 ASSERT(m_offset > 0);
1445 int exceptionCode = 0;
1447 m_text2->deleteData(0, m_offset, exceptionCode);
1448 ASSERT(exceptionCode == 0);
1450 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
1451 ASSERT(exceptionCode == 0);
1453 ASSERT(m_text2->previousSibling()->isTextNode());
1454 ASSERT(m_text2->previousSibling() == m_text1);
1457 //------------------------------------------------------------------------------------------
1458 // PasteMarkupCommandImpl
1460 PasteMarkupCommandImpl::PasteMarkupCommandImpl(DocumentImpl *document, const DOMString &markupString, const DOM::DOMString &baseURL)
1461 : CompositeEditCommandImpl(document), m_markupString(markupString), m_baseURL(baseURL)
1463 ASSERT(!m_markupString.isEmpty());
1466 PasteMarkupCommandImpl::~PasteMarkupCommandImpl()
1470 int PasteMarkupCommandImpl::commandID() const
1472 return PasteMarkupCommandID;
1475 void PasteMarkupCommandImpl::doApply()
1477 DocumentFragmentImpl *root = static_cast<HTMLElementImpl *>(document()->documentElement())->createContextualFragment(m_markupString);
1480 if (!m_baseURL.isEmpty() && m_baseURL != document()->baseURL()) {
1481 root->recursive_completeURLs(m_baseURL.string());
1484 NodeImpl *firstChild = root->firstChild();
1485 NodeImpl *lastChild = root->lastChild();
1489 KHTMLSelection selection = endingSelection();
1491 // Delete the current selection, or collapse whitespace, as needed
1492 if (selection.state() == KHTMLSelection::RANGE)
1495 deleteCollapsibleWhitespace();
1497 selection = endingSelection();
1498 ASSERT(!selection.isEmpty());
1500 if (firstChild == lastChild && firstChild->isTextNode()) {
1501 // Simple text paste. Treat as if the text were typed.
1502 inputText(static_cast<TextImpl *>(firstChild)->data());
1505 // HTML fragment paste.
1506 NodeImpl *beforeNode = firstChild;
1507 NodeImpl *node = firstChild->nextSibling();
1509 insertNodeAt(firstChild, selection.startNode(), selection.startOffset());
1511 // Insert the nodes from the fragment
1513 NodeImpl *next = node->nextSibling();
1514 insertNodeAfter(node, beforeNode);
1520 // Find the last leaf and place the caret after it.
1521 NodeImpl *leaf = lastChild;
1523 NodeImpl *nextChild = leaf->lastChild();
1529 setEndingSelection(DOMPosition(leaf, leaf->caretMaxOffset()));
1533 //------------------------------------------------------------------------------------------
1534 // RemoveNodeCommandImpl
1536 RemoveNodeCommandImpl::RemoveNodeCommandImpl(DocumentImpl *document, NodeImpl *removeChild)
1537 : EditCommandImpl(document), m_parent(0), m_removeChild(removeChild), m_refChild(0)
1539 ASSERT(m_removeChild);
1540 m_removeChild->ref();
1542 m_parent = m_removeChild->parentNode();
1546 NodeListImpl *children = m_parent->childNodes();
1547 for (int i = children->length(); i >= 0; i--) {
1548 NodeImpl *node = children->item(i);
1549 if (node == m_removeChild)
1558 RemoveNodeCommandImpl::~RemoveNodeCommandImpl()
1563 m_removeChild->deref();
1565 m_refChild->deref();
1568 int RemoveNodeCommandImpl::commandID() const
1570 return RemoveNodeCommandID;
1573 void RemoveNodeCommandImpl::doApply()
1576 ASSERT(m_removeChild);
1578 int exceptionCode = 0;
1579 m_parent->removeChild(m_removeChild, exceptionCode);
1580 ASSERT(exceptionCode == 0);
1583 void RemoveNodeCommandImpl::doUnapply()
1586 ASSERT(m_removeChild);
1588 int exceptionCode = 0;
1590 m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
1592 m_parent->appendChild(m_removeChild, exceptionCode);
1593 ASSERT(exceptionCode == 0);
1596 //------------------------------------------------------------------------------------------
1597 // RemoveNodeAndPruneCommandImpl
1599 RemoveNodeAndPruneCommandImpl::RemoveNodeAndPruneCommandImpl(DocumentImpl *document, NodeImpl *removeChild)
1600 : CompositeEditCommandImpl(document), m_removeChild(removeChild)
1602 ASSERT(m_removeChild);
1603 m_removeChild->ref();
1606 RemoveNodeAndPruneCommandImpl::~RemoveNodeAndPruneCommandImpl()
1609 m_removeChild->deref();
1612 int RemoveNodeAndPruneCommandImpl::commandID() const
1614 return RemoveNodeAndPruneCommandID;
1617 void RemoveNodeAndPruneCommandImpl::doApply()
1619 NodeImpl *editableBlock = m_removeChild->containingEditableBlock();
1620 NodeImpl *pruneNode = m_removeChild;
1621 NodeImpl *node = pruneNode->traversePreviousNode();
1622 removeNode(pruneNode);
1624 if (editableBlock != node->containingEditableBlock() || !shouldPruneNode(node))
1627 node = node->traversePreviousNode();
1628 removeNode(pruneNode);
1632 //------------------------------------------------------------------------------------------
1633 // SplitTextNodeCommandImpl
1635 SplitTextNodeCommandImpl::SplitTextNodeCommandImpl(DocumentImpl *document, TextImpl *text, long offset)
1636 : EditCommandImpl(document), m_text1(0), m_text2(text), m_offset(offset)
1639 ASSERT(m_text2->length() > 0);
1644 SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl()
1652 int SplitTextNodeCommandImpl::commandID() const
1654 return SplitTextNodeCommandID;
1657 void SplitTextNodeCommandImpl::doApply()
1660 ASSERT(m_offset > 0);
1662 int exceptionCode = 0;
1664 // EDIT FIXME: This should use better smarts for figuring out which portion
1665 // of the split to copy (based on their comparitive sizes). We should also
1666 // just use the DOM's splitText function.
1669 // create only if needed.
1670 // if reapplying, this object will already exist.
1671 m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode));
1672 ASSERT(exceptionCode == 0);
1677 m_text2->deleteData(0, m_offset, exceptionCode);
1678 ASSERT(exceptionCode == 0);
1680 m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
1681 ASSERT(exceptionCode == 0);
1683 ASSERT(m_text2->previousSibling()->isTextNode());
1684 ASSERT(m_text2->previousSibling() == m_text1);
1687 void SplitTextNodeCommandImpl::doUnapply()
1692 ASSERT(m_text1->nextSibling() == m_text2);
1694 int exceptionCode = 0;
1695 m_text2->insertData(0, m_text1->data(), exceptionCode);
1696 ASSERT(exceptionCode == 0);
1698 m_text2->parent()->removeChild(m_text1, exceptionCode);
1699 ASSERT(exceptionCode == 0);
1701 m_offset = m_text1->length();
1704 //------------------------------------------------------------------------------------------
1705 // TypingCommandImpl
1707 TypingCommandImpl::TypingCommandImpl(DocumentImpl *document)
1708 : CompositeEditCommandImpl(document), m_openForMoreTyping(true)
1712 TypingCommandImpl::~TypingCommandImpl()
1716 int TypingCommandImpl::commandID() const
1718 return TypingCommandID;
1721 void TypingCommandImpl::doApply()
1725 void TypingCommandImpl::insertText(const DOM::DOMString &text)
1727 if (m_cmds.count() == 0) {
1728 InputTextCommand cmd(document());
1729 applyCommandToComposite(cmd);
1733 EditCommand lastCommand = m_cmds.last();
1734 if (lastCommand.commandID() == InputTextCommandID) {
1735 static_cast<InputTextCommand &>(lastCommand).input(text);
1738 InputTextCommand cmd(document());
1739 applyCommandToComposite(cmd);
1745 void TypingCommandImpl::insertNewline()
1747 InputNewlineCommand cmd(document());
1748 applyCommandToComposite(cmd);
1751 void TypingCommandImpl::issueCommandForDeleteKey()
1753 KHTMLSelection selectionToDelete = endingSelection();
1754 ASSERT(selectionToDelete.state() != KHTMLSelection::NONE);
1756 if (selectionToDelete.state() == KHTMLSelection::CARET)
1757 selectionToDelete = KHTMLSelection(selectionToDelete.startPosition().previousCharacterPosition(), selectionToDelete.startPosition());
1758 deleteSelection(selectionToDelete);
1761 void TypingCommandImpl::deleteKeyPressed()
1763 // EDIT FIXME: The ifdef'ed out code below should be re-enabled.
1764 // In order for this to happen, the deleteCharacter case
1765 // needs work. Specifically, the caret-positioning code
1766 // and whitespace-handling code in DeleteSelectionCommandImpl::doApply()
1767 // needs to be factored out so it can be used again here.
1768 // Until that work is done, issueCommandForDeleteKey() does the
1769 // right thing, but less efficiently and with the cost of more
1771 issueCommandForDeleteKey();
1773 if (m_cmds.count() == 0) {
1774 issueCommandForDeleteKey();
1777 EditCommand lastCommand = m_cmds.last();
1778 if (lastCommand.commandID() == InputTextCommandID) {
1779 InputTextCommand cmd = static_cast<InputTextCommand &>(lastCommand);
1780 cmd.deleteCharacter();
1781 if (cmd.charactersAdded() == 0) {
1785 else if (lastCommand.commandID() == InputNewlineCommandID) {
1786 lastCommand.unapply();
1787 removeCommand(lastCommand);
1790 issueCommandForDeleteKey();
1796 void TypingCommandImpl::removeCommand(const EditCommand &cmd)
1798 // NOTE: If the passed-in command is the last command in the
1799 // composite, we could remove all traces of this typing command
1800 // from the system, including the undo chain. Other editors do
1801 // not do this, but we could.
1804 if (m_cmds.count() == 0)
1805 setEndingSelection(startingSelection());
1807 setEndingSelection(m_cmds.last().endingSelection());
1810 //------------------------------------------------------------------------------------------