1 /*
2  * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
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.
24  */
25 
26 #include "htmlediting_impl.h"
27 #include "editor.h"
28 
29 #include "css/cssproperties.h"
30 #include "css/css_valueimpl.h"
31 #include "dom/css_value.h"
32 #include "html/html_elementimpl.h"
33 #include "html/html_imageimpl.h"
34 #include "rendering/render_object.h"
35 #include "rendering/render_style.h"
36 #include "rendering/render_text.h"
37 #include "xml/dom_docimpl.h"
38 #include "xml/dom_elementimpl.h"
39 #include "xml/dom_positioniterator.h"
40 #include "xml/dom_stringimpl.h"
41 #include "xml/dom_textimpl.h"
42 #include "xml/dom2_rangeimpl.h"
43 #include "xml/dom2_viewsimpl.h"
44 
45 #include "khtml_part.h"
46 #include "khtmlview.h"
47 
48 #include <QScopedPointer>
49 #include <limits.h>
50 
51 using DOM::AttrImpl;
52 using DOM::CSSPrimitiveValue;
53 using DOM::CSSPrimitiveValueImpl;
54 using DOM::CSSProperty;
55 using DOM::CSSStyleDeclarationImpl;
56 using DOM::CSSValueImpl;
57 using DOM::DocumentFragmentImpl;
58 using DOM::DocumentImpl;
59 using DOM::DOMString;
60 using DOM::DOMStringImpl;
61 using DOM::EditingTextImpl;
62 using DOM::PositionIterator;
63 using DOM::ElementImpl;
64 using DOM::HTMLElementImpl;
65 using DOM::HTMLImageElementImpl;
66 using DOM::NamedAttrMapImpl;
67 using DOM::Node;
68 using DOM::NodeImpl;
69 using DOM::NodeListImpl;
70 using DOM::Position;
71 using DOM::Range;
72 using DOM::RangeImpl;
73 using DOM::Selection;
74 using DOM::TextImpl;
75 using DOM::TreeWalkerImpl;
76 using DOM::Editor;
77 
78 #define DEBUG_COMMANDS 0
79 
80 namespace khtml
81 {
82 
isNBSP(const QChar & c)83 static inline bool isNBSP(const QChar &c)
84 {
85     return c == QChar(0xa0);
86 }
87 
isWS(const QChar & c)88 static inline bool isWS(const QChar &c)
89 {
90     return c.isSpace() && c != QChar(0xa0);
91 }
92 
isWS(const DOMString & text)93 static inline bool isWS(const DOMString &text)
94 {
95     if (text.length() != 1) {
96         return false;
97     }
98 
99     return isWS(text[0]);
100 }
101 
isWS(const Position & pos)102 static inline bool isWS(const Position &pos)
103 {
104     if (!pos.node()) {
105         return false;
106     }
107 
108     if (!pos.node()->isTextNode()) {
109         return false;
110     }
111 
112     const DOMString &string = static_cast<TextImpl *>(pos.node())->data();
113     return isWS(string[pos.offset()]);
114 }
115 
shouldPruneNode(NodeImpl * node)116 static bool shouldPruneNode(NodeImpl *node)
117 {
118     if (!node) {
119         return false;
120     }
121 
122     RenderObject *renderer = node->renderer();
123     if (!renderer) {
124         return true;
125     }
126 
127     if (node->hasChildNodes()) {
128         return false;
129     }
130 
131     if (node->rootEditableElement() == node) {
132         return false;
133     }
134 
135     if (renderer->isBR() || renderer->isReplaced()) {
136         return false;
137     }
138 
139     if (node->isTextNode()) {
140         TextImpl *text = static_cast<TextImpl *>(node);
141         if (text->length() == 0) {
142             return true;
143         }
144         return false;
145     }
146 
147     if (!node->isHTMLElement()/* && !node->isXMLElementNode()*/) {
148         return false;
149     }
150 
151     if (node->id() == ID_BODY) {
152         return false;
153     }
154 
155     if (!node->isContentEditable()) {
156         return false;
157     }
158 
159     return true;
160 }
161 
leadingWhitespacePosition(const Position & pos)162 static Position leadingWhitespacePosition(const Position &pos)
163 {
164     assert(pos.notEmpty());
165 
166     Selection selection(pos);
167     Position prev = pos.previousCharacterPosition();
168     if (prev != pos && prev.node()->inSameContainingBlockFlowElement(pos.node()) && prev.node()->isTextNode()) {
169         DOMString string = static_cast<TextImpl *>(prev.node())->data();
170         if (isWS(string[prev.offset()])) {
171             return prev;
172         }
173     }
174 
175     return Position();
176 }
177 
trailingWhitespacePosition(const Position & pos)178 static Position trailingWhitespacePosition(const Position &pos)
179 {
180     assert(pos.notEmpty());
181 
182     if (pos.node()->isTextNode()) {
183         TextImpl *textNode = static_cast<TextImpl *>(pos.node());
184         if (pos.offset() >= (long)textNode->length()) {
185             Position next = pos.nextCharacterPosition();
186             if (next != pos && next.node()->inSameContainingBlockFlowElement(pos.node()) && next.node()->isTextNode()) {
187                 DOMString string = static_cast<TextImpl *>(next.node())->data();
188                 if (isWS(string[0])) {
189                     return next;
190                 }
191             }
192         } else {
193             DOMString string = static_cast<TextImpl *>(pos.node())->data();
194             if (isWS(string[pos.offset()])) {
195                 return pos;
196             }
197         }
198     }
199 
200     return Position();
201 }
202 
textNodesAreJoinable(TextImpl * text1,TextImpl * text2)203 static bool textNodesAreJoinable(TextImpl *text1, TextImpl *text2)
204 {
205     assert(text1);
206     assert(text2);
207 
208     return (text1->nextSibling() == text2);
209 }
210 
nonBreakingSpaceString()211 static DOMString &nonBreakingSpaceString()
212 {
213     static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
214     return nonBreakingSpaceString;
215 }
216 
styleSpanClassString()217 static DOMString &styleSpanClassString()
218 {
219     static DOMString styleSpanClassString = "khtml-style-span";
220     return styleSpanClassString;
221 }
222 
223 //------------------------------------------------------------------------------------------
224 // EditCommandImpl
225 
EditCommandImpl(DocumentImpl * document)226 EditCommandImpl::EditCommandImpl(DocumentImpl *document)
227     : SharedCommandImpl(), m_document(document), m_state(NotApplied), m_parent(nullptr)
228 {
229     assert(m_document);
230     assert(m_document->part());
231     m_document->ref();
232     m_startingSelection = m_document->part()->caret();
233     m_endingSelection = m_startingSelection;
234 }
235 
~EditCommandImpl()236 EditCommandImpl::~EditCommandImpl()
237 {
238     m_document->deref();
239 }
240 
apply()241 void EditCommandImpl::apply()
242 {
243     assert(m_document);
244     assert(m_document->part());
245     assert(state() == NotApplied);
246 
247     doApply();
248 
249     m_state = Applied;
250 
251     if (!isCompositeStep()) {
252         m_document->part()->editor()->appliedEditing(this);
253     }
254 }
255 
unapply()256 void EditCommandImpl::unapply()
257 {
258     assert(m_document);
259     assert(m_document->part());
260     assert(state() == Applied);
261 
262     doUnapply();
263 
264     m_state = NotApplied;
265 
266     if (!isCompositeStep()) {
267         m_document->part()->editor()->unappliedEditing(this);
268     }
269 }
270 
reapply()271 void EditCommandImpl::reapply()
272 {
273     assert(m_document);
274     assert(m_document->part());
275     assert(state() == NotApplied);
276 
277     doReapply();
278 
279     m_state = Applied;
280 
281     if (!isCompositeStep()) {
282         m_document->part()->editor()->reappliedEditing(this);
283     }
284 }
285 
doReapply()286 void EditCommandImpl::doReapply()
287 {
288     doApply();
289 }
290 
setStartingSelection(const Selection & s)291 void EditCommandImpl::setStartingSelection(const Selection &s)
292 {
293     m_startingSelection = s;
294     EditCommandImpl *cmd = parent();
295     while (cmd) {
296         cmd->m_startingSelection = s;
297         cmd = cmd->parent();
298     }
299 }
300 
setEndingSelection(const Selection & s)301 void EditCommandImpl::setEndingSelection(const Selection &s)
302 {
303     m_endingSelection = s;
304     EditCommandImpl *cmd = parent();
305     while (cmd) {
306         cmd->m_endingSelection = s;
307         cmd = cmd->parent();
308     }
309 }
310 
parent() const311 EditCommandImpl *EditCommandImpl::parent() const
312 {
313     return m_parent.get();
314 }
315 
setParent(EditCommandImpl * cmd)316 void EditCommandImpl::setParent(EditCommandImpl *cmd)
317 {
318     m_parent = cmd;
319 }
320 
321 //------------------------------------------------------------------------------------------
322 // CompositeEditCommandImpl
323 
CompositeEditCommandImpl(DocumentImpl * document)324 CompositeEditCommandImpl::CompositeEditCommandImpl(DocumentImpl *document)
325     : EditCommandImpl(document)
326 {
327 }
328 
~CompositeEditCommandImpl()329 CompositeEditCommandImpl::~CompositeEditCommandImpl()
330 {
331 }
332 
doUnapply()333 void CompositeEditCommandImpl::doUnapply()
334 {
335     if (m_cmds.count() == 0) {
336         return;
337     }
338 
339     for (int i = m_cmds.count() - 1; i >= 0; --i) {
340         m_cmds[i]->unapply();
341     }
342 
343     setState(NotApplied);
344 }
345 
doReapply()346 void CompositeEditCommandImpl::doReapply()
347 {
348     if (m_cmds.count() == 0) {
349         return;
350     }
351     QMutableListIterator<RefPtr<EditCommandImpl> > it(m_cmds);
352     while (it.hasNext()) {
353         it.next()->reapply();
354     }
355 
356     setState(Applied);
357 }
358 
359 //
360 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
361 //
applyCommandToComposite(PassRefPtr<EditCommandImpl> cmd)362 void CompositeEditCommandImpl::applyCommandToComposite(PassRefPtr<EditCommandImpl> cmd)
363 {
364     cmd->setStartingSelection(endingSelection());//###?
365     cmd->setEndingSelection(endingSelection());
366     cmd->setParent(this);
367     cmd->apply();
368     m_cmds.append(cmd);
369 }
370 
insertNodeBefore(NodeImpl * insertChild,NodeImpl * refChild)371 void CompositeEditCommandImpl::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
372 {
373     RefPtr<InsertNodeBeforeCommandImpl> cmd = new InsertNodeBeforeCommandImpl(document(), insertChild, refChild);
374     applyCommandToComposite(cmd);
375 }
376 
insertNodeAfter(NodeImpl * insertChild,NodeImpl * refChild)377 void CompositeEditCommandImpl::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
378 {
379     if (refChild->parentNode()->lastChild() == refChild) {
380         appendNode(refChild->parentNode(), insertChild);
381     } else {
382         assert(refChild->nextSibling());
383         insertNodeBefore(insertChild, refChild->nextSibling());
384     }
385 }
386 
insertNodeAt(NodeImpl * insertChild,NodeImpl * refChild,long offset)387 void CompositeEditCommandImpl::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
388 {
389     if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
390         NodeImpl *child = refChild->firstChild();
391         for (long i = 0; child && i < offset; i++) {
392             child = child->nextSibling();
393         }
394         if (child) {
395             insertNodeBefore(insertChild, child);
396         } else {
397             appendNode(refChild, insertChild);
398         }
399     } else if (refChild->caretMinOffset() >= offset) {
400         insertNodeBefore(insertChild, refChild);
401     } else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
402         splitTextNode(static_cast<TextImpl *>(refChild), offset);
403         insertNodeBefore(insertChild, refChild);
404     } else {
405         insertNodeAfter(insertChild, refChild);
406     }
407 }
408 
appendNode(NodeImpl * parent,NodeImpl * appendChild)409 void CompositeEditCommandImpl::appendNode(NodeImpl *parent, NodeImpl *appendChild)
410 {
411     RefPtr<AppendNodeCommandImpl> cmd = new AppendNodeCommandImpl(document(), parent, appendChild);
412     applyCommandToComposite(cmd);
413 }
414 
removeNode(NodeImpl * removeChild)415 void CompositeEditCommandImpl::removeNode(NodeImpl *removeChild)
416 {
417     RefPtr<RemoveNodeCommandImpl> cmd = new RemoveNodeCommandImpl(document(), removeChild);
418     applyCommandToComposite(cmd);
419 }
420 
removeNodeAndPrune(NodeImpl * pruneNode,NodeImpl * stopNode)421 void CompositeEditCommandImpl::removeNodeAndPrune(NodeImpl *pruneNode, NodeImpl *stopNode)
422 {
423     RefPtr<RemoveNodeAndPruneCommandImpl> cmd = new RemoveNodeAndPruneCommandImpl(document(), pruneNode, stopNode);
424     applyCommandToComposite(cmd);
425 }
426 
removeNodePreservingChildren(NodeImpl * removeChild)427 void CompositeEditCommandImpl::removeNodePreservingChildren(NodeImpl *removeChild)
428 {
429     RefPtr<RemoveNodePreservingChildrenCommandImpl> cmd = new RemoveNodePreservingChildrenCommandImpl(document(), removeChild);
430     applyCommandToComposite(cmd);
431 }
432 
splitTextNode(TextImpl * text,long offset)433 void CompositeEditCommandImpl::splitTextNode(TextImpl *text, long offset)
434 {
435     RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, offset);
436     applyCommandToComposite(cmd);
437 }
438 
joinTextNodes(TextImpl * text1,TextImpl * text2)439 void CompositeEditCommandImpl::joinTextNodes(TextImpl *text1, TextImpl *text2)
440 {
441     RefPtr<JoinTextNodesCommandImpl> cmd = new JoinTextNodesCommandImpl(document(), text1, text2);
442     applyCommandToComposite(cmd);
443 }
444 
inputText(const DOMString & text)445 void CompositeEditCommandImpl::inputText(const DOMString &text)
446 {
447     RefPtr<InputTextCommandImpl> cmd = new InputTextCommandImpl(document());
448     applyCommandToComposite(cmd);
449     cmd->input(text);
450 }
451 
insertText(TextImpl * node,long offset,const DOMString & text)452 void CompositeEditCommandImpl::insertText(TextImpl *node, long offset, const DOMString &text)
453 {
454     RefPtr<InsertTextCommandImpl> cmd = new InsertTextCommandImpl(document(), node, offset, text);
455     applyCommandToComposite(cmd);
456 }
457 
deleteText(TextImpl * node,long offset,long count)458 void CompositeEditCommandImpl::deleteText(TextImpl *node, long offset, long count)
459 {
460     RefPtr<DeleteTextCommandImpl> cmd = new DeleteTextCommandImpl(document(), node, offset, count);
461     applyCommandToComposite(cmd);
462 }
463 
replaceText(TextImpl * node,long offset,long count,const DOMString & replacementText)464 void CompositeEditCommandImpl::replaceText(TextImpl *node, long offset, long count, const DOMString &replacementText)
465 {
466     RefPtr<DeleteTextCommandImpl> deleteCommand = new DeleteTextCommandImpl(document(), node, offset, count);
467     applyCommandToComposite(deleteCommand);
468     RefPtr<InsertTextCommandImpl> insertCommand = new InsertTextCommandImpl(document(), node, offset, replacementText);
469     applyCommandToComposite(insertCommand);
470 }
471 
deleteSelection()472 void CompositeEditCommandImpl::deleteSelection()
473 {
474     if (endingSelection().state() == Selection::RANGE) {
475         RefPtr<DeleteSelectionCommandImpl> cmd = new DeleteSelectionCommandImpl(document());
476         applyCommandToComposite(cmd);
477     }
478 }
479 
deleteSelection(const Selection & selection)480 void CompositeEditCommandImpl::deleteSelection(const Selection &selection)
481 {
482     if (selection.state() == Selection::RANGE) {
483         RefPtr<DeleteSelectionCommandImpl> cmd = new DeleteSelectionCommandImpl(document(), selection);
484         applyCommandToComposite(cmd);
485     }
486 }
487 
deleteCollapsibleWhitespace()488 void CompositeEditCommandImpl::deleteCollapsibleWhitespace()
489 {
490     RefPtr<DeleteCollapsibleWhitespaceCommandImpl> cmd = new DeleteCollapsibleWhitespaceCommandImpl(document());
491     applyCommandToComposite(cmd);
492 }
493 
deleteCollapsibleWhitespace(const Selection & selection)494 void CompositeEditCommandImpl::deleteCollapsibleWhitespace(const Selection &selection)
495 {
496     RefPtr<DeleteCollapsibleWhitespaceCommandImpl> cmd = new DeleteCollapsibleWhitespaceCommandImpl(document(), selection);
497     applyCommandToComposite(cmd);
498 }
499 
removeCSSProperty(CSSStyleDeclarationImpl * decl,int property)500 void CompositeEditCommandImpl::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
501 {
502     RefPtr<RemoveCSSPropertyCommandImpl> cmd = new RemoveCSSPropertyCommandImpl(document(), decl, property);
503     applyCommandToComposite(cmd);
504 }
505 
removeNodeAttribute(ElementImpl * element,int attribute)506 void CompositeEditCommandImpl::removeNodeAttribute(ElementImpl *element, int attribute)
507 {
508     RefPtr<RemoveNodeAttributeCommandImpl> cmd = new RemoveNodeAttributeCommandImpl(document(), element, attribute);
509     applyCommandToComposite(cmd);
510 }
511 
setNodeAttribute(ElementImpl * element,int attribute,const DOMString & value)512 void CompositeEditCommandImpl::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
513 {
514     RefPtr<SetNodeAttributeCommandImpl> cmd = new SetNodeAttributeCommandImpl(document(), element, attribute, value);
515     applyCommandToComposite(cmd);
516 }
517 
createTypingStyleElement() const518 ElementImpl *CompositeEditCommandImpl::createTypingStyleElement() const
519 {
520     ElementImpl *styleElement = document()->createHTMLElement("SPAN");
521 
522     styleElement->setAttribute(ATTR_STYLE, document()->part()->editor()->typingStyle()->cssText().implementation());
523 
524     styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
525 
526     return styleElement;
527 }
528 
529 //==========================================================================================
530 // Concrete commands
531 //------------------------------------------------------------------------------------------
532 // AppendNodeCommandImpl
533 
AppendNodeCommandImpl(DocumentImpl * document,NodeImpl * parentNode,NodeImpl * appendChild)534 AppendNodeCommandImpl::AppendNodeCommandImpl(DocumentImpl *document, NodeImpl *parentNode, NodeImpl *appendChild)
535     : EditCommandImpl(document), m_parentNode(parentNode), m_appendChild(appendChild)
536 {
537     assert(m_parentNode);
538     m_parentNode->ref();
539 
540     assert(m_appendChild);
541     m_appendChild->ref();
542 }
543 
~AppendNodeCommandImpl()544 AppendNodeCommandImpl::~AppendNodeCommandImpl()
545 {
546     if (m_parentNode) {
547         m_parentNode->deref();
548     }
549     if (m_appendChild) {
550         m_appendChild->deref();
551     }
552 }
553 
doApply()554 void AppendNodeCommandImpl::doApply()
555 {
556     assert(m_parentNode);
557     assert(m_appendChild);
558 
559     int exceptionCode = 0;
560     m_parentNode->appendChild(m_appendChild, exceptionCode);
561     assert(exceptionCode == 0);
562 }
563 
doUnapply()564 void AppendNodeCommandImpl::doUnapply()
565 {
566     assert(m_parentNode);
567     assert(m_appendChild);
568     assert(state() == Applied);
569 
570     int exceptionCode = 0;
571     m_parentNode->removeChild(m_appendChild, exceptionCode);
572     assert(exceptionCode == 0);
573 }
574 
575 //------------------------------------------------------------------------------------------
576 // ApplyStyleCommandImpl
577 
ApplyStyleCommandImpl(DocumentImpl * document,CSSStyleDeclarationImpl * style)578 ApplyStyleCommandImpl::ApplyStyleCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *style)
579     : CompositeEditCommandImpl(document), m_style(style)
580 {
581     assert(m_style);
582     m_style->ref();
583 }
584 
~ApplyStyleCommandImpl()585 ApplyStyleCommandImpl::~ApplyStyleCommandImpl()
586 {
587     assert(m_style);
588     m_style->deref();
589 }
590 
isBlockLevelStyle(const CSSStyleDeclarationImpl * style)591 static bool isBlockLevelStyle(const CSSStyleDeclarationImpl *style)
592 {
593     QListIterator<CSSProperty *> it(*(style->values()));
594     while (it.hasNext()) {
595         CSSProperty *property = it.next();
596         switch (property->id()) {
597         case CSS_PROP_TEXT_ALIGN:
598             return true;
599             /*case CSS_PROP_FONT_WEIGHT:
600                 if (strcasecmp(property->value()->cssText(), "bold") == 0)
601                     styleChange.applyBold = true;
602                 else
603                     styleChange.cssStyle += property->cssText();
604                 break;
605             case CSS_PROP_FONT_STYLE: {
606                     DOMString cssText(property->value()->cssText());
607                     if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0)
608                         styleChange.applyItalic = true;
609                     else
610                         styleChange.cssStyle += property->cssText();
611                 }
612                 break;
613             default:
614                 styleChange.cssStyle += property->cssText();
615                 break;*/
616         }
617     }
618     return false;
619 }
620 
applyStyleChangeOnTheNode(ElementImpl * element,CSSStyleDeclarationImpl * style)621 static void applyStyleChangeOnTheNode(ElementImpl *element, CSSStyleDeclarationImpl *style)
622 {
623     QScopedPointer<CSSStyleDeclarationImpl> computedStyle(
624             element->document()->defaultView()->getComputedStyle(element, nullptr));
625     assert(!computedStyle.isNull());
626 #ifdef DEBUG_COMMANDS
627     qCDebug(KHTML_LOG) << "[change style]" << element;
628 #endif
629 
630     QListIterator<CSSProperty *> it(*(style->values()));
631     while (it.hasNext()) {
632         CSSProperty *property = it.next();
633         CSSValueImpl *computedValue = computedStyle->getPropertyCSSValue(property->id());
634         DOMString newValue = property->value()->cssText();
635 #ifdef DEBUG_COMMANDS
636         qCDebug(KHTML_LOG) << "[new value]:" << property->cssText();
637         qCDebug(KHTML_LOG) << "[computedValue]:" << computedValue->cssText();
638 #endif
639         if (strcasecmp(computedValue->cssText(), newValue)) {
640             // we can do better and avoid parsing property
641             element->getInlineStyleDecls()->setProperty(property->id(), newValue);
642         }
643     }
644 }
645 
doApply()646 void ApplyStyleCommandImpl::doApply()
647 {
648     if (endingSelection().state() != Selection::RANGE) {
649         return;
650     }
651 
652     // adjust to the positions we want to use for applying style
653     Position start(endingSelection().start().equivalentDownstreamPosition().equivalentRangeCompliantPosition());
654     Position end(endingSelection().end().equivalentUpstreamPosition());
655 #ifdef DEBUG_COMMANDS
656     qCDebug(KHTML_LOG) << "[APPLY STYLE]" << start << end;
657     printEnclosingBlockTree(start.node()->enclosingBlockFlowElement());
658 #endif
659 
660     if (isBlockLevelStyle(m_style)) {
661 #ifdef DEBUG_COMMANDS
662         qCDebug(KHTML_LOG) << "[APPLY BLOCK LEVEL STYLE]";
663 #endif
664         ElementImpl *startBlock = start.node()->enclosingBlockFlowElement();
665         ElementImpl *endBlock   = end.node()->enclosingBlockFlowElement();
666 #ifdef DEBUG_COMMANDS
667         qCDebug(KHTML_LOG) << startBlock << startBlock->nodeName();
668 #endif
669         if (startBlock == endBlock && startBlock == start.node()->rootEditableElement()) {
670             ElementImpl *block = document()->createHTMLElement("DIV");
671 #ifdef DEBUG_COMMANDS
672             qCDebug(KHTML_LOG) << "[Create DIV with Style:]" << m_style->cssText();
673 #endif
674             block->setAttribute(ATTR_STYLE, m_style->cssText());
675             for (NodeImpl *node = startBlock->firstChild(); node; node = startBlock->firstChild()) {
676 #ifdef DEBUG_COMMANDS
677                 qCDebug(KHTML_LOG) << "[reparent node]" << node << node->nodeName();
678 #endif
679                 removeNode(node);
680                 appendNode(block, node);
681             }
682             appendNode(startBlock, block);
683         } else if (startBlock == endBlock) {
684             // StyleChange styleChange = computeStyleChange(Position(startBlock, 0), m_style);
685             //qCDebug(KHTML_LOG) << "[Modify block with style change:]" << styleChange.cssStyle;
686             applyStyleChangeOnTheNode(startBlock, m_style);
687             // startBlock->setAttribute(ATTR_STYLE, styleChange.cssStyle);
688         }
689         return;
690     }
691 
692     // remove style from the selection
693     removeStyle(start, end);
694     bool splitStart = splitTextAtStartIfNeeded(start, end);
695     if (splitStart) {
696         start = endingSelection().start();
697         end = endingSelection().end();
698     }
699     splitTextAtEndIfNeeded(start, end);
700     start = endingSelection().start();
701     end = endingSelection().end();
702 
703 #ifdef DEBUG_COMMANDS
704     qCDebug(KHTML_LOG) << "[start;end]" << start << end;
705 #endif
706     if (start.node() == end.node()) {
707         // simple case...start and end are the same node
708         applyStyleIfNeeded(start.node(), end.node());
709     } else {
710         NodeImpl *node = start.node();
711         while (1) {
712             if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
713                 NodeImpl *runStart = node;
714                 while (1) {
715                     if (runStart->parentNode() != node->parentNode() || node->isHTMLElement() || node == end.node() ||
716                             (node->renderer() && !node->renderer()->isInline())) {
717                         applyStyleIfNeeded(runStart, node);
718                         break;
719                     }
720                     node = node->traverseNextNode();
721                 }
722             }
723             if (node == end.node()) {
724                 break;
725             }
726             node = node->traverseNextNode();
727         }
728     }
729 }
730 
731 //------------------------------------------------------------------------------------------
732 // ApplyStyleCommandImpl: style-removal helpers
733 
isHTMLStyleNode(HTMLElementImpl * elem)734 bool ApplyStyleCommandImpl::isHTMLStyleNode(HTMLElementImpl *elem)
735 {
736     QListIterator<CSSProperty *> it(*(style()->values()));
737     while (it.hasNext()) {
738         CSSProperty *property = it.next();
739         switch (property->id()) {
740         case CSS_PROP_FONT_WEIGHT:
741             if (elem->id() == ID_B) {
742                 return true;
743             }
744             break;
745         case CSS_PROP_FONT_STYLE:
746             if (elem->id() == ID_I) {
747                 return true;
748             }
749             break;
750         }
751     }
752 
753     return false;
754 }
755 
removeHTMLStyleNode(HTMLElementImpl * elem)756 void ApplyStyleCommandImpl::removeHTMLStyleNode(HTMLElementImpl *elem)
757 {
758     // This node can be removed.
759     // EDIT FIXME: This does not handle the case where the node
760     // has attributes. But how often do people add attributes to <B> tags?
761     // Not so often I think.
762     assert(elem);
763     removeNodePreservingChildren(elem);
764 }
765 
removeCSSStyle(HTMLElementImpl * elem)766 void ApplyStyleCommandImpl::removeCSSStyle(HTMLElementImpl *elem)
767 {
768     assert(elem);
769 
770     CSSStyleDeclarationImpl *decl = elem->inlineStyleDecls();
771     if (!decl) {
772         return;
773     }
774 
775     QListIterator<CSSProperty *> it(*(style()->values()));
776     while (it.hasNext()) {
777         CSSProperty *property = it.next();
778         if (decl->getPropertyCSSValue(property->id())) {
779             removeCSSProperty(decl, property->id());
780         }
781     }
782 
783     if (elem->id() == ID_SPAN) {
784         // Check to see if the span is one we added to apply style.
785         // If it is, and there are no more attributes on the span other than our
786         // class marker, remove the span.
787         NamedAttrMapImpl *map = elem->attributes();
788         if (map && (map->length() == 1 || (map->length() == 2 && elem->getAttribute(ATTR_STYLE).isEmpty())) &&
789                 elem->getAttribute(ATTR_CLASS) == styleSpanClassString()) {
790             removeNodePreservingChildren(elem);
791         }
792     }
793 }
794 
removeStyle(const Position & start,const Position & end)795 void ApplyStyleCommandImpl::removeStyle(const Position &start, const Position &end)
796 {
797     NodeImpl *node = start.node();
798     while (1) {
799         NodeImpl *next = node->traverseNextNode();
800         if (node->isHTMLElement() && nodeFullySelected(node)) {
801             HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
802             if (isHTMLStyleNode(elem)) {
803                 removeHTMLStyleNode(elem);
804             } else {
805                 removeCSSStyle(elem);
806             }
807         }
808         if (node == end.node()) {
809             break;
810         }
811         node = next;
812     }
813 }
814 
nodeFullySelected(const NodeImpl * node) const815 bool ApplyStyleCommandImpl::nodeFullySelected(const NodeImpl *node) const
816 {
817     assert(node);
818 
819     Position end(endingSelection().end().equivalentUpstreamPosition());
820 
821     if (node == end.node()) {
822         return end.offset() >= node->caretMaxOffset();
823     }
824 
825     for (NodeImpl *child = node->lastChild(); child; child = child->lastChild()) {
826         if (child == end.node()) {
827             return end.offset() >= child->caretMaxOffset();
828         }
829     }
830 
831     return node == end.node() || !node->isAncestor(end.node());
832 }
833 
834 //------------------------------------------------------------------------------------------
835 // ApplyStyleCommandImpl: style-application helpers
836 
splitTextAtStartIfNeeded(const Position & start,const Position & end)837 bool ApplyStyleCommandImpl::splitTextAtStartIfNeeded(const Position &start, const Position &end)
838 {
839     if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
840 #ifdef DEBUG_COMMANDS
841         qCDebug(KHTML_LOG) << "[split start]" << start.offset() << start.node()->caretMinOffset() << start.node()->caretMaxOffset();
842 #endif
843         long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
844         TextImpl *text = static_cast<TextImpl *>(start.node());
845         RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, start.offset());
846         applyCommandToComposite(cmd);
847         setEndingSelection(Selection(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment)));
848         return true;
849     }
850     return false;
851 }
852 
splitTextAtEndIfNeeded(const Position & start,const Position & end)853 NodeImpl *ApplyStyleCommandImpl::splitTextAtEndIfNeeded(const Position &start, const Position &end)
854 {
855     if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
856 #ifdef DEBUG_COMMANDS
857         qCDebug(KHTML_LOG) << "[split end]" << end.offset() << end.node()->caretMinOffset() << end.node()->caretMaxOffset();
858 #endif
859         TextImpl *text = static_cast<TextImpl *>(end.node());
860         RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, end.offset());
861         applyCommandToComposite(cmd);
862         NodeImpl *startNode = start.node() == end.node() ? cmd->node()->previousSibling() : start.node();
863         assert(startNode);
864         setEndingSelection(Selection(Position(startNode, start.offset()), Position(cmd->node()->previousSibling(), cmd->node()->previousSibling()->caretMaxOffset())));
865         return cmd->node()->previousSibling();
866     }
867     return end.node();
868 }
869 
surroundNodeRangeWithElement(NodeImpl * startNode,NodeImpl * endNode,ElementImpl * element)870 void ApplyStyleCommandImpl::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
871 {
872     assert(startNode);
873     assert(endNode);
874     assert(element);
875 
876     NodeImpl *node = startNode;
877     while (1) {
878         NodeImpl *next = node->traverseNextNode();
879         if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
880             removeNode(node);
881             appendNode(element, node);
882         }
883         if (node == endNode) {
884             break;
885         }
886         node = next;
887     }
888 }
889 
checkIfNewStylingNeeded(ElementImpl * element,CSSStyleDeclarationImpl * style)890 static bool /*ApplyStyleCommandImpl::*/checkIfNewStylingNeeded(ElementImpl *element, CSSStyleDeclarationImpl *style)
891 {
892     QScopedPointer<CSSStyleDeclarationImpl> computedStyle(
893             element->document()->defaultView()->getComputedStyle(element, nullptr));
894     assert(!computedStyle.isNull());
895 #ifdef DEBUG_COMMANDS
896     qCDebug(KHTML_LOG) << "[check styling]" << element;
897 #endif
898 
899     QListIterator<CSSProperty *> it(*(style->values()));
900     while (it.hasNext()) {
901         CSSProperty *property = it.next();
902         CSSValueImpl *computedValue = computedStyle->getPropertyCSSValue(property->id());
903         DOMString newValue = property->value()->cssText();
904 #ifdef DEBUG_COMMANDS
905         qCDebug(KHTML_LOG) << "[new value]:" << property->cssText();
906         qCDebug(KHTML_LOG) << "[computedValue]:" << computedValue->cssText();
907 #endif
908         if (strcasecmp(computedValue->cssText(), newValue)) {
909             return true;
910         }
911     }
912     return false;
913 }
914 
applyStyleIfNeeded(DOM::NodeImpl * startNode,DOM::NodeImpl * endNode)915 void ApplyStyleCommandImpl::applyStyleIfNeeded(DOM::NodeImpl *startNode, DOM::NodeImpl *endNode)
916 {
917     ElementImpl *parent = Position(startNode, 0).element();
918     if (!checkIfNewStylingNeeded(parent, style())) {
919         return;
920     }
921     ElementImpl *styleElement = nullptr;
922     if (parent->id() == ID_SPAN && parent->firstChild() == startNode && parent->lastChild() == endNode) {
923         styleElement = parent;
924     } else {
925         styleElement = document()->createHTMLElement("SPAN");
926         styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
927         insertNodeBefore(styleElement, startNode);
928         surroundNodeRangeWithElement(startNode, endNode, styleElement);
929     }
930     applyStyleChangeOnTheNode(styleElement, style());
931 }
932 
currentlyHasStyle(const Position & pos,const CSSProperty * property) const933 bool ApplyStyleCommandImpl::currentlyHasStyle(const Position &pos, const CSSProperty *property) const
934 {
935     assert(pos.notEmpty());
936     qCDebug(KHTML_LOG) << pos;
937     CSSStyleDeclarationImpl *decl = document()->defaultView()->getComputedStyle(pos.element(), nullptr);
938     assert(decl);
939     CSSValueImpl *value = decl->getPropertyCSSValue(property->id());
940     return strcasecmp(value->cssText(), property->value()->cssText()) == 0;
941 }
942 
computeStyleChange(const Position & insertionPoint,CSSStyleDeclarationImpl * style)943 ApplyStyleCommandImpl::StyleChange ApplyStyleCommandImpl::computeStyleChange(const Position &insertionPoint, CSSStyleDeclarationImpl *style)
944 {
945     assert(insertionPoint.notEmpty());
946     assert(style);
947 
948     StyleChange styleChange;
949 
950     QListIterator<CSSProperty *> it(*(style->values()));
951     while (it.hasNext()) {
952         CSSProperty *property = it.next();
953 #ifdef DEBUG_COMMANDS
954         qCDebug(KHTML_LOG) << "[CSS property]:" << property->cssText();
955 #endif
956         if (!currentlyHasStyle(insertionPoint, property)) {
957 #ifdef DEBUG_COMMANDS
958             qCDebug(KHTML_LOG) << "[Add to style change]";
959 #endif
960             switch (property->id()) {
961             case CSS_PROP_FONT_WEIGHT:
962                 if (strcasecmp(property->value()->cssText(), "bold") == 0) {
963                     styleChange.applyBold = true;
964                 } else {
965                     styleChange.cssStyle += property->cssText();
966                 }
967                 break;
968             case CSS_PROP_FONT_STYLE: {
969                 DOMString cssText(property->value()->cssText());
970                 if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0) {
971                     styleChange.applyItalic = true;
972                 } else {
973                     styleChange.cssStyle += property->cssText();
974                 }
975             }
976             break;
977             default:
978                 styleChange.cssStyle += property->cssText();
979                 break;
980             }
981         }
982     }
983     return styleChange;
984 }
985 
positionInsertionPoint(Position pos)986 Position ApplyStyleCommandImpl::positionInsertionPoint(Position pos)
987 {
988     if (pos.node()->isTextNode() && (pos.offset() > 0 && pos.offset() < pos.node()->maxOffset())) {
989         RefPtr<SplitTextNodeCommandImpl> split = new SplitTextNodeCommandImpl(document(), static_cast<TextImpl *>(pos.node()), pos.offset());
990         split->apply();
991         pos = Position(split->node(), 0);
992     }
993 #if 0
994     // EDIT FIXME: If modified to work with the internals of applying style,
995     // this code can work to optimize cases where a style change is taking place on
996     // a boundary between nodes where one of the nodes has the desired style. In other
997     // words, it is possible for content to be merged into existing nodes rather than adding
998     // additional markup.
999     if (currentlyHasStyle(pos)) {
1000         return pos;
1001     }
1002 
1003     // try next node
1004     if (pos.offset() >= pos.node()->caretMaxOffset()) {
1005         NodeImpl *nextNode = pos.node()->traverseNextNode();
1006         if (nextNode) {
1007             Position next = Position(nextNode, 0);
1008             if (currentlyHasStyle(next)) {
1009                 return next;
1010             }
1011         }
1012     }
1013 
1014     // try previous node
1015     if (pos.offset() <= pos.node()->caretMinOffset()) {
1016         NodeImpl *prevNode = pos.node()->traversePreviousNode();
1017         if (prevNode) {
1018             Position prev = Position(prevNode, prevNode->maxOffset());
1019             if (currentlyHasStyle(prev)) {
1020                 return prev;
1021             }
1022         }
1023     }
1024 #endif
1025 
1026     return pos;
1027 }
1028 
1029 //------------------------------------------------------------------------------------------
1030 // DeleteCollapsibleWhitespaceCommandImpl
1031 
DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl * document)1032 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document)
1033     : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_hasSelectionToCollapse(false)
1034 {
1035 }
1036 
DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl * document,const Selection & selection)1037 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document, const Selection &selection)
1038     : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_selectionToCollapse(selection), m_hasSelectionToCollapse(true)
1039 {
1040 }
1041 
~DeleteCollapsibleWhitespaceCommandImpl()1042 DeleteCollapsibleWhitespaceCommandImpl::~DeleteCollapsibleWhitespaceCommandImpl()
1043 {
1044 }
1045 
shouldDeleteUpstreamPosition(const Position & pos)1046 static bool shouldDeleteUpstreamPosition(const Position &pos)
1047 {
1048     if (!pos.node()->isTextNode()) {
1049         return false;
1050     }
1051 
1052     RenderObject *renderer = pos.node()->renderer();
1053     if (!renderer) {
1054         return true;
1055     }
1056 
1057     TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1058     if (pos.offset() >= (long)textNode->length()) {
1059         return false;
1060     }
1061 
1062     if (pos.isLastRenderedPositionInEditableBlock()) {
1063         return false;
1064     }
1065 
1066     if (pos.isFirstRenderedPositionOnLine() || pos.isLastRenderedPositionOnLine()) {
1067         return false;
1068     }
1069 
1070     return false;
1071     // TODO we need to match DOM - Rendered offset first
1072 //    RenderText *textRenderer = static_cast<RenderText *>(renderer);
1073 //    for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
1074 //        if (pos.offset() < box->m_start) {
1075 //            return true;
1076 //        }
1077 //        if (pos.offset() >= box->m_start && pos.offset() < box->m_start + box->m_len)
1078 //            return false;
1079 //    }
1080 //
1081 //    return true;
1082 }
1083 
deleteWhitespace(const Position & pos)1084 Position DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace(const Position &pos)
1085 {
1086     Position upstream = pos.equivalentUpstreamPosition();
1087     Position downstream = pos.equivalentDownstreamPosition();
1088 #ifdef DEBUG_COMMANDS
1089     qCDebug(KHTML_LOG) << "[pos]" << pos;
1090     qCDebug(KHTML_LOG) << "[upstream:downstream]" << upstream << downstream;
1091     printEnclosingBlockTree(pos.node());
1092 #endif
1093 
1094     bool del = shouldDeleteUpstreamPosition(upstream);
1095 #ifdef DEBUG_COMMANDS
1096     qCDebug(KHTML_LOG) << "[delete upstream]" << del;
1097 #endif
1098 
1099     if (upstream == downstream) {
1100         return upstream;
1101     }
1102 
1103 #ifdef DEBUG_COMMANDS
1104     PositionIterator iter(upstream);
1105     qCDebug(KHTML_LOG) << "[before print]";
1106     for (iter.next(); iter.current() != downstream; iter.next()) {
1107         qCDebug(KHTML_LOG) << "[iterate]" << iter.current();
1108     }
1109     qCDebug(KHTML_LOG) << "[after print]";
1110 #endif
1111 
1112     PositionIterator it(upstream);
1113     Position deleteStart = upstream;
1114     if (!del) {
1115         deleteStart = it.peekNext();
1116         if (deleteStart == downstream) {
1117             return upstream;
1118         }
1119     }
1120 
1121     Position endingPosition = upstream;
1122 
1123     while (it.current() != downstream) {
1124         Position next = it.peekNext();
1125 #ifdef DEBUG_COMMANDS
1126         qCDebug(KHTML_LOG) << "[iterate and delete]" << next;
1127 #endif
1128         if (next.node() != deleteStart.node()) {
1129             // TODO assert(deleteStart.node()->isTextNode());
1130             if (deleteStart.node()->isTextNode()) {
1131                 TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node());
1132                 unsigned long count = it.current().offset() - deleteStart.offset();
1133                 if (count == textNode->length()) {
1134 #ifdef DEBUG_COMMANDS
1135                     qCDebug(KHTML_LOG) << "   removeNodeAndPrune 1:" << textNode;
1136 #endif
1137                     if (textNode == endingPosition.node()) {
1138                         endingPosition = Position(next.node(), next.node()->caretMinOffset());
1139                     }
1140                     removeNodeAndPrune(textNode);
1141                 } else {
1142 #ifdef DEBUG_COMMANDS
1143                     qCDebug(KHTML_LOG) << "   deleteText 1:" <<  textNode << "t len:" << textNode->length() << "start:" <<  deleteStart.offset() << "del len:" << (it.current().offset() - deleteStart.offset());
1144 #endif
1145                     deleteText(textNode, deleteStart.offset(), count);
1146                 }
1147             } else {
1148 #ifdef DEBUG_COMMANDS
1149                 qCDebug(KHTML_LOG) << "[not text node is not supported yet]";
1150 #endif
1151             }
1152             deleteStart = next;
1153         } else if (next == downstream) {
1154             assert(deleteStart.node() == downstream.node());
1155             assert(downstream.node()->isTextNode());
1156             TextImpl *textNode = static_cast<TextImpl *>(deleteStart.node());
1157             unsigned long count = downstream.offset() - deleteStart.offset();
1158             assert(count <= textNode->length());
1159             if (count == textNode->length()) {
1160 #ifdef DEBUG_COMMANDS
1161                 qCDebug(KHTML_LOG) << "   removeNodeAndPrune 2:" << textNode;
1162 #endif
1163                 removeNodeAndPrune(textNode);
1164             } else {
1165 #ifdef DEBUG_COMMANDS
1166                 qCDebug(KHTML_LOG) << "   deleteText 2:" << textNode << "t len:" <<  textNode->length() << "start:" << deleteStart.offset() << "del len:" <<  count;
1167 #endif
1168                 deleteText(textNode, deleteStart.offset(), count);
1169                 m_charactersDeleted = count;
1170                 endingPosition = Position(downstream.node(), downstream.offset() - m_charactersDeleted);
1171             }
1172         }
1173 
1174         it.setPosition(next);
1175     }
1176 
1177     return endingPosition;
1178 }
1179 
doApply()1180 void DeleteCollapsibleWhitespaceCommandImpl::doApply()
1181 {
1182     // If selection has not been set to a custom selection when the command was created,
1183     // use the current ending selection.
1184     if (!m_hasSelectionToCollapse) {
1185         m_selectionToCollapse = endingSelection();
1186     }
1187     int state = m_selectionToCollapse.state();
1188     if (state == Selection::CARET) {
1189         Position endPosition = deleteWhitespace(m_selectionToCollapse.start());
1190         setEndingSelection(endPosition);
1191 #ifdef DEBUG_COMMANDS
1192         qCDebug(KHTML_LOG) << "-----------------------------------------------------";
1193 #endif
1194     } else if (state == Selection::RANGE) {
1195         Position startPosition = deleteWhitespace(m_selectionToCollapse.start());
1196 #ifdef DEBUG_COMMANDS
1197         qCDebug(KHTML_LOG) <<  "-----------------------------------------------------";
1198 #endif
1199         Position endPosition = m_selectionToCollapse.end();
1200         if (m_charactersDeleted > 0 && startPosition.node() == endPosition.node()) {
1201 #ifdef DEBUG_COMMANDS
1202             qCDebug(KHTML_LOG) << "adjust end position by" << m_charactersDeleted;
1203 #endif
1204             endPosition = Position(endPosition.node(), endPosition.offset() - m_charactersDeleted);
1205         }
1206         endPosition = deleteWhitespace(endPosition);
1207         setEndingSelection(Selection(startPosition, endPosition));
1208 #ifdef DEBUG_COMMANDS
1209         qCDebug(KHTML_LOG) << "=====================================================";
1210 #endif
1211     }
1212 }
1213 
1214 //------------------------------------------------------------------------------------------
1215 // DeleteSelectionCommandImpl
1216 
DeleteSelectionCommandImpl(DocumentImpl * document)1217 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document)
1218     : CompositeEditCommandImpl(document), m_hasSelectionToDelete(false)
1219 {
1220 }
1221 
DeleteSelectionCommandImpl(DocumentImpl * document,const Selection & selection)1222 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document, const Selection &selection)
1223     : CompositeEditCommandImpl(document), m_selectionToDelete(selection), m_hasSelectionToDelete(true)
1224 {
1225 }
1226 
~DeleteSelectionCommandImpl()1227 DeleteSelectionCommandImpl::~DeleteSelectionCommandImpl()
1228 {
1229 }
1230 
joinTextNodesWithSameStyle()1231 void DeleteSelectionCommandImpl::joinTextNodesWithSameStyle()
1232 {
1233     Selection selection = endingSelection();
1234 
1235     if (selection.state() != Selection::CARET) {
1236         return;
1237     }
1238 
1239     Position pos(selection.start());
1240 
1241     if (!pos.node()->isTextNode()) {
1242         return;
1243     }
1244 
1245     TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1246 
1247     if (pos.offset() == 0) {
1248         PositionIterator it(pos);
1249         Position prev = it.previous();
1250         if (prev == pos) {
1251             return;
1252         }
1253         if (prev.node()->isTextNode()) {
1254             TextImpl *prevTextNode = static_cast<TextImpl *>(prev.node());
1255             if (textNodesAreJoinable(prevTextNode, textNode)) {
1256                 joinTextNodes(prevTextNode, textNode);
1257                 setEndingSelection(Position(textNode, prevTextNode->length()));
1258 #ifdef DEBUG_COMMANDS
1259                 qCDebug(KHTML_LOG) << "joinTextNodesWithSameStyle [1]";
1260 #endif
1261             }
1262         }
1263     } else if (pos.offset() == (long)textNode->length()) {
1264         PositionIterator it(pos);
1265         Position next = it.next();
1266         if (next == pos) {
1267             return;
1268         }
1269         if (next.node()->isTextNode()) {
1270             TextImpl *nextTextNode = static_cast<TextImpl *>(next.node());
1271             if (textNodesAreJoinable(textNode, nextTextNode)) {
1272                 joinTextNodes(textNode, nextTextNode);
1273                 setEndingSelection(Position(nextTextNode, pos.offset()));
1274 #ifdef DEBUG_COMMANDS
1275                 qCDebug(KHTML_LOG) << "joinTextNodesWithSameStyle [2]";
1276 #endif
1277             }
1278         }
1279     }
1280 }
1281 
containsOnlyWhitespace(const Position & start,const Position & end)1282 bool DeleteSelectionCommandImpl::containsOnlyWhitespace(const Position &start, const Position &end)
1283 {
1284     // Returns whether the range contains only whitespace characters.
1285     // This is inclusive of the start, but not of the end.
1286     PositionIterator it(start);
1287     while (!it.atEnd()) {
1288         if (!it.current().node()->isTextNode()) {
1289             return false;
1290         }
1291         const DOMString &text = static_cast<TextImpl *>(it.current().node())->data();
1292         // EDIT FIXME: signed/unsigned mismatch
1293         if (text.length() > INT_MAX) {
1294             return false;
1295         }
1296         if (it.current().offset() < (int)text.length() && !isWS(text[it.current().offset()])) {
1297             return false;
1298         }
1299         it.next();
1300         if (it.current() == end) {
1301             break;
1302         }
1303     }
1304     return true;
1305 }
1306 
deleteContentInsideNode(NodeImpl * node,int startOffset,int endOffset)1307 void DeleteSelectionCommandImpl::deleteContentInsideNode(NodeImpl *node, int startOffset, int endOffset)
1308 {
1309 #ifdef DEBUG_COMMANDS
1310     qCDebug(KHTML_LOG) << "[Delete content inside node]" << node << startOffset << endOffset;
1311 #endif
1312     if (node->isTextNode()) {
1313         // check if nothing to delete
1314         if (startOffset == endOffset) {
1315             return;
1316         }
1317         // check if node is fully covered then remove node completely
1318         if (!startOffset && endOffset == node->maxOffset()) {
1319             removeNodeAndPrune(node);
1320             return;
1321         }
1322         // delete only substring
1323         deleteText(static_cast<TextImpl *>(node), startOffset, endOffset - startOffset);
1324         return;
1325     }
1326 #ifdef DEBUG_COMMANDS
1327     qCDebug(KHTML_LOG) << "[non-text node] not supported";
1328 #endif
1329 }
1330 
deleteContentBeforeOffset(NodeImpl * node,int offset)1331 void DeleteSelectionCommandImpl::deleteContentBeforeOffset(NodeImpl *node, int offset)
1332 {
1333     deleteContentInsideNode(node, 0, offset);
1334 }
1335 
deleteContentAfterOffset(NodeImpl * node,int offset)1336 void DeleteSelectionCommandImpl::deleteContentAfterOffset(NodeImpl *node, int offset)
1337 {
1338     if (node->isTextNode()) {
1339         deleteContentInsideNode(node, offset, node->maxOffset());
1340     }
1341 }
1342 
doApply()1343 void DeleteSelectionCommandImpl::doApply()
1344 {
1345     // If selection has not been set to a custom selection when the command was created,
1346     // use the current ending selection.
1347     if (!m_hasSelectionToDelete) {
1348         m_selectionToDelete = endingSelection();
1349     }
1350 
1351     if (m_selectionToDelete.state() != Selection::RANGE) {
1352         return;
1353     }
1354 
1355     deleteCollapsibleWhitespace(m_selectionToDelete);
1356     Selection selection = endingSelection();
1357 
1358     Position upstreamStart(selection.start().equivalentUpstreamPosition());
1359     Position downstreamStart(selection.start().equivalentDownstreamPosition());
1360     Position upstreamEnd(selection.end().equivalentUpstreamPosition());
1361     Position downstreamEnd(selection.end().equivalentDownstreamPosition());
1362 
1363     NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement();
1364     NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement();
1365 
1366 #ifdef DEBUG_COMMANDS
1367     qCDebug(KHTML_LOG) << "[Delete:Start]" << upstreamStart << downstreamStart;
1368     qCDebug(KHTML_LOG) << "[Delete:End]" << upstreamEnd << downstreamEnd;
1369     printEnclosingBlockTree(upstreamStart.node());
1370 #endif
1371     if (startBlock != endBlock) {
1372         printEnclosingBlockTree(downstreamEnd.node());
1373     }
1374 
1375     if (upstreamStart == downstreamEnd)
1376         // after collapsing whitespace, selection is empty...no work to do
1377     {
1378         return;
1379     }
1380 
1381     // remove all the nodes that are completely covered by the selection
1382     if (upstreamStart.node() != downstreamEnd.node()) {
1383         NodeImpl *node, *next;
1384         for (node = upstreamStart.node()->traverseNextNode(); node && node != downstreamEnd.node(); node = next) {
1385 #ifdef DEBUG_COMMANDS
1386             qCDebug(KHTML_LOG) << "[traverse and delete]" << node << (node->renderer() && node->renderer()->isEditable());
1387 #endif
1388             next = node->traverseNextNode();
1389             if (node->renderer() && node->renderer()->isEditable()) {
1390                 removeNode(node);    // removeAndPrune?
1391             }
1392         }
1393     }
1394 
1395     // if we have different blocks then merge content of the second into first one
1396     if (startBlock != endBlock && startBlock->parentNode() == endBlock->parentNode()) {
1397         NodeImpl *node = endBlock->firstChild();
1398         while (node) {
1399             NodeImpl *moveNode = node;
1400             node = node->nextSibling();
1401             removeNode(moveNode);
1402             appendNode(startBlock, moveNode);
1403         }
1404     }
1405 
1406     if (upstreamStart.node() == downstreamEnd.node()) {
1407         deleteContentInsideNode(upstreamEnd.node(), upstreamStart.offset(), downstreamEnd.offset());
1408     } else {
1409         deleteContentAfterOffset(upstreamStart.node(), upstreamStart.offset());
1410         deleteContentBeforeOffset(downstreamEnd.node(), downstreamEnd.offset());
1411     }
1412 
1413     setEndingSelection(upstreamStart);
1414 #if 0
1415     Position endingPosition;
1416     bool adjustEndingPositionDownstream = false;
1417 
1418     bool onlyWhitespace = containsOnlyWhitespace(upstreamStart, downstreamEnd);
1419     qCDebug(KHTML_LOG) << "[OnlyWhitespace]" << onlyWhitespace;
1420 
1421     bool startCompletelySelected = !onlyWhitespace &&
1422                                    (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset() &&
1423                                     ((downstreamStart.node() != upstreamEnd.node()) ||
1424                                      (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset())));
1425 
1426     bool endCompletelySelected = !onlyWhitespace &&
1427                                  (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset() &&
1428                                   ((downstreamStart.node() != upstreamEnd.node()) ||
1429                                    (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset())));
1430 
1431     qCDebug(KHTML_LOG) << "[{start:end}CompletelySelected]" << startCompletelySelected << endCompletelySelected;
1432 
1433     unsigned long startRenderedOffset = downstreamStart.renderedOffset();
1434 
1435     bool startAtStartOfRootEditableElement = startRenderedOffset == 0 && downstreamStart.inFirstEditableInRootEditableElement();
1436     bool startAtStartOfBlock = startAtStartOfRootEditableElement ||
1437                                (startRenderedOffset == 0 && downstreamStart.inFirstEditableInContainingEditableBlock());
1438     bool endAtEndOfBlock = downstreamEnd.isLastRenderedPositionInEditableBlock();
1439 
1440     qCDebug(KHTML_LOG) << "[startAtStartOfRootEditableElement]" << startAtStartOfRootEditableElement;
1441     qCDebug(KHTML_LOG) << "[startAtStartOfBlock]" << startAtStartOfBlock;
1442     qCDebug(KHTML_LOG) << "[endAtEndOfBlock]" << endAtEndOfBlock;
1443 
1444     NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement();
1445     NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement();
1446     bool startBlockEndBlockAreSiblings = startBlock->parentNode() == endBlock->parentNode();
1447 
1448     qCDebug(KHTML_LOG) << "[startBlockEndBlockAreSiblings]" << startBlockEndBlockAreSiblings << startBlock << endBlock;
1449 
1450     debugPosition("upstreamStart:       ", upstreamStart);
1451     debugPosition("downstreamStart:     ", downstreamStart);
1452     debugPosition("upstreamEnd:         ", upstreamEnd);
1453     debugPosition("downstreamEnd:       ", downstreamEnd);
1454     qCDebug(KHTML_LOG) << "start selected:" << (startCompletelySelected ? "YES" : "NO");
1455     qCDebug(KHTML_LOG) << "at start block:" << (startAtStartOfBlock ? "YES" : "NO");
1456     qCDebug(KHTML_LOG) << "at start root block:" << (startAtStartOfRootEditableElement ? "YES" : "NO");
1457     qCDebug(KHTML_LOG) << "at end block:" << (endAtEndOfBlock ? "YES" : "NO");
1458     qCDebug(KHTML_LOG) << "only whitespace:" << (onlyWhitespace ? "YES" : "NO");
1459 
1460     // Determine where to put the caret after the deletion
1461     if (startAtStartOfBlock) {
1462         qCDebug(KHTML_LOG) << "ending position case 1";
1463         endingPosition = Position(startBlock, 0);
1464         adjustEndingPositionDownstream = true;
1465     } else if (!startCompletelySelected) {
1466         qCDebug(KHTML_LOG) << "ending position case 2";
1467         endingPosition = upstreamEnd; // FIXME ??????????? upstreamStart;
1468         if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1) {
1469             adjustEndingPositionDownstream = true;
1470         }
1471     } else if (upstreamStart != downstreamStart) {
1472         qCDebug(KHTML_LOG) << "ending position case 3";
1473         endingPosition = upstreamStart;
1474         if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1) {
1475             adjustEndingPositionDownstream = true;
1476         }
1477     }
1478 
1479     //
1480     // Figure out the whitespace conversions to do
1481     //
1482     if ((startAtStartOfBlock && !endAtEndOfBlock) || (!startCompletelySelected && adjustEndingPositionDownstream)) {
1483         // convert trailing whitespace
1484         Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
1485         if (trailing.notEmpty()) {
1486             debugPosition("convertTrailingWhitespace: ", trailing);
1487             Position collapse = trailing.nextCharacterPosition();
1488             if (collapse != trailing) {
1489                 deleteCollapsibleWhitespace(collapse);
1490             }
1491             TextImpl *textNode = static_cast<TextImpl *>(trailing.node());
1492             replaceText(textNode, trailing.offset(), 1, nonBreakingSpaceString());
1493         }
1494     } else if (!startAtStartOfBlock && endAtEndOfBlock) {
1495         // convert leading whitespace
1496         Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
1497         if (leading.notEmpty()) {
1498             debugPosition("convertLeadingWhitespace:  ", leading);
1499             TextImpl *textNode = static_cast<TextImpl *>(leading.node());
1500             replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
1501         }
1502     } else if (!startAtStartOfBlock && !endAtEndOfBlock) {
1503         // convert contiguous whitespace
1504         Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
1505         Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
1506         if (leading.notEmpty() && trailing.notEmpty()) {
1507             debugPosition("convertLeadingWhitespace [contiguous]:  ", leading);
1508             TextImpl *textNode = static_cast<TextImpl *>(leading.node());
1509             replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
1510         }
1511     }
1512 
1513     //
1514     // Do the delete
1515     //
1516     NodeImpl *n = downstreamStart.node()->traverseNextNode();
1517     qCDebug(KHTML_LOG) << "[n]" << n;
1518 
1519     // work on start node
1520     if (startCompletelySelected) {
1521         qCDebug(KHTML_LOG) << "start node delete case 1";
1522         removeNodeAndPrune(downstreamStart.node(), startBlock);
1523     } else if (onlyWhitespace) {
1524         // Selection only contains whitespace. This is really a special-case to
1525         // handle significant whitespace that is collapsed at the end of a line,
1526         // but also handles deleting a space in mid-line.
1527         qCDebug(KHTML_LOG) << "start node delete case 2";
1528         assert(upstreamStart.node()->isTextNode());
1529         TextImpl *text = static_cast<TextImpl *>(upstreamStart.node());
1530         int offset = upstreamStart.offset();
1531         // EDIT FIXME: Signed/unsigned mismatch
1532         int length = text->length();
1533         if (length == upstreamStart.offset()) {
1534             offset--;
1535         }
1536         // FIXME ??? deleteText(text, offset, 1);
1537     } else if (downstreamStart.node()->isTextNode()) {
1538         qCDebug(KHTML_LOG) << "start node delete case 3";
1539         TextImpl *text = static_cast<TextImpl *>(downstreamStart.node());
1540         int endOffset = text == upstreamEnd.node() ? upstreamEnd.offset() : text->length();
1541         if (endOffset > downstreamStart.offset()) {
1542             deleteText(text, downstreamStart.offset(), endOffset - downstreamStart.offset());
1543         }
1544     } else {
1545         // we have clipped the end of a non-text element
1546         // the offset must be 1 here. if it is, do nothing and move on.
1547         qCDebug(KHTML_LOG) << "start node delete case 4";
1548         assert(downstreamStart.offset() == 1);
1549     }
1550 
1551     if (n && !onlyWhitespace && downstreamStart.node() != upstreamEnd.node()) {
1552         // work on intermediate nodes
1553         while (n && n != upstreamEnd.node()) {
1554             NodeImpl *d = n;
1555             n = n->traverseNextNode();
1556             if (d->renderer() && d->renderer()->isEditable()) {
1557                 removeNodeAndPrune(d, startBlock);
1558             }
1559         }
1560         if (!n) {
1561             return;
1562         }
1563 
1564         // work on end node
1565         assert(n == upstreamEnd.node());
1566         if (endCompletelySelected) {
1567             removeNodeAndPrune(upstreamEnd.node(), startBlock);
1568         } else if (upstreamEnd.node()->isTextNode()) {
1569             if (upstreamEnd.offset() > 0) {
1570                 TextImpl *text = static_cast<TextImpl *>(upstreamEnd.node());
1571                 deleteText(text, 0, upstreamEnd.offset());
1572             }
1573         } else {
1574             // we have clipped the beginning of a non-text element
1575             // the offset must be 0 here. if it is, do nothing and move on.
1576             assert(downstreamStart.offset() == 0);
1577         }
1578     }
1579 
1580     // Do block merge if start and end of selection are in different blocks
1581     // and the blocks are siblings. This is a first cut at this rule arrived
1582     // at by doing a bunch of edits and settling on the behavior that made
1583     // the most sense. This could change in the future as we get more
1584     // experience with how this should behave.
1585     if (startBlock != endBlock && startBlockEndBlockAreSiblings) {
1586         qCDebug(KHTML_LOG) << "merging content to start block";
1587         NodeImpl *node = endBlock->firstChild();
1588         while (node) {
1589             NodeImpl *moveNode = node;
1590             node = node->nextSibling();
1591             removeNode(moveNode);
1592             appendNode(startBlock, moveNode);
1593         }
1594     }
1595 
1596     if (adjustEndingPositionDownstream) {
1597         qCDebug(KHTML_LOG) << "adjust ending position downstream";
1598         endingPosition = endingPosition.equivalentDownstreamPosition();
1599     }
1600 
1601     debugPosition("ending position:     ", endingPosition);
1602     setEndingSelection(endingPosition);
1603 
1604     qCDebug(KHTML_LOG) << "-----------------------------------------------------";
1605 #endif
1606 }
1607 
1608 //------------------------------------------------------------------------------------------
1609 // DeleteTextCommandImpl
1610 
DeleteTextCommandImpl(DocumentImpl * document,TextImpl * node,long offset,long count)1611 DeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, long count)
1612     : EditCommandImpl(document), m_node(node), m_offset(offset), m_count(count)
1613 {
1614     assert(m_node);
1615     assert(m_offset >= 0);
1616     assert(m_count >= 0);
1617 
1618     m_node->ref();
1619 }
1620 
~DeleteTextCommandImpl()1621 DeleteTextCommandImpl::~DeleteTextCommandImpl()
1622 {
1623     if (m_node) {
1624         m_node->deref();
1625     }
1626 }
1627 
doApply()1628 void DeleteTextCommandImpl::doApply()
1629 {
1630     assert(m_node);
1631 
1632     int exceptionCode = 0;
1633     m_text = m_node->substringData(m_offset, m_count, exceptionCode);
1634     assert(exceptionCode == 0);
1635 
1636     m_node->deleteData(m_offset, m_count, exceptionCode);
1637     assert(exceptionCode == 0);
1638 }
1639 
doUnapply()1640 void DeleteTextCommandImpl::doUnapply()
1641 {
1642     assert(m_node);
1643     assert(!m_text.isEmpty());
1644 
1645     int exceptionCode = 0;
1646     m_node->insertData(m_offset, m_text, exceptionCode);
1647     assert(exceptionCode == 0);
1648 }
1649 
1650 //------------------------------------------------------------------------------------------
1651 // InputNewlineCommandImpl
1652 
InputNewlineCommandImpl(DocumentImpl * document)1653 InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document)
1654     : CompositeEditCommandImpl(document)
1655 {
1656 }
1657 
~InputNewlineCommandImpl()1658 InputNewlineCommandImpl::~InputNewlineCommandImpl()
1659 {
1660 }
1661 
insertNodeAfterPosition(NodeImpl * node,const Position & pos)1662 void InputNewlineCommandImpl::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
1663 {
1664     // Insert the BR after the caret position. In the case the
1665     // position is a block, do an append. We don't want to insert
1666     // the BR *after* the block.
1667     Position upstream(pos.equivalentUpstreamPosition());
1668     NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
1669     if (cb == pos.node()) {
1670         appendNode(cb, node);
1671     } else {
1672         insertNodeAfter(node, pos.node());
1673     }
1674 }
1675 
insertNodeBeforePosition(NodeImpl * node,const Position & pos)1676 void InputNewlineCommandImpl::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
1677 {
1678     // Insert the BR after the caret position. In the case the
1679     // position is a block, do an append. We don't want to insert
1680     // the BR *before* the block.
1681     Position upstream(pos.equivalentUpstreamPosition());
1682     NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
1683     if (cb == pos.node()) {
1684         appendNode(cb, node);
1685     } else {
1686         insertNodeBefore(node, pos.node());
1687     }
1688 }
1689 
doApply()1690 void InputNewlineCommandImpl::doApply()
1691 {
1692     deleteSelection();
1693     Selection selection = endingSelection();
1694     int exceptionCode = 0;
1695 
1696     NodeImpl *enclosingBlock = selection.start().node()->enclosingBlockFlowElement();
1697     qCDebug(KHTML_LOG) << enclosingBlock->nodeName();
1698     if (enclosingBlock->id() == ID_LI) {
1699         // need to insert new list item or split existing one into 2
1700         // consider example: <li>x<u>x<b>x|x</b>x</u>x</li> (| - caret position)
1701         // result should look like: <li>x<u>x<b>x</b></u></li><li><u>|x<b>x</b></u></li>
1702         // idea is to walk up to the li item and split and reattach correspondent nodes
1703 #ifdef DEBUG_COMMANDS
1704         qCDebug(KHTML_LOG) << "[insert new list item]" << selection;
1705         printEnclosingBlockTree(selection.start().node());
1706 #endif
1707         Position pos(selection.start().equivalentDownstreamPosition());
1708         NodeImpl *node = pos.node();
1709         bool atBlockStart = pos.atStartOfContainingEditableBlock();
1710         bool atBlockEnd = pos.isLastRenderedPositionInEditableBlock();
1711         // split text node into 2 if we are in the middle
1712         if (node->isTextNode() && !atBlockStart && !atBlockEnd) {
1713             TextImpl *textNode = static_cast<TextImpl *>(node);
1714             TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
1715             deleteText(textNode, 0, pos.offset());
1716             insertNodeBefore(textBeforeNode, textNode);
1717             pos = Position(textNode, 0);
1718             setEndingSelection(pos);
1719 
1720             // walk up and reattach
1721             while (true) {
1722 #ifdef DEBUG_COMMANDS
1723                 qCDebug(KHTML_LOG) << "[handle node]" << node;
1724                 printEnclosingBlockTree(enclosingBlock->parent());
1725 #endif
1726                 NodeImpl *parent = node->parent();
1727                 // FIXME copy attributes, styles etc too
1728                 RefPtr<NodeImpl> newParent = parent->cloneNode(false);
1729                 insertNodeAfter(newParent.get(), parent);
1730                 for (NodeImpl *nextSibling = nullptr; node; node = nextSibling) {
1731 #ifdef DEBUG_COMMANDS
1732                     qCDebug(KHTML_LOG) << "[reattach sibling]" << node;
1733 #endif
1734                     nextSibling = node->nextSibling();
1735                     removeNode(node);
1736                     appendNode(newParent.get(), node);
1737                 }
1738                 node = newParent.get();
1739                 if (parent == enclosingBlock) {
1740                     break;
1741                 }
1742             }
1743         } else if (node->isTextNode()) {
1744             // insert <br> node either as previous list or the next one
1745             if (atBlockStart) {
1746                 ElementImpl *listItem = document()->createHTMLElement("LI");
1747                 insertNodeBefore(listItem, enclosingBlock);
1748             } else {
1749                 ElementImpl *listItem = document()->createHTMLElement("LI");
1750                 insertNodeAfter(listItem, enclosingBlock);
1751             }
1752         }
1753 
1754 #ifdef DEBUG_COMMANDS
1755         qCDebug(KHTML_LOG) << "[result]";
1756         printEnclosingBlockTree(enclosingBlock->parent());
1757 #endif
1758         // FIXME set selection after operation
1759         return;
1760     }
1761 
1762     ElementImpl *breakNode = document()->createHTMLElement("BR");
1763     // assert(exceptionCode == 0);
1764 
1765 #ifdef DEBUG_COMMANDS
1766     qCDebug(KHTML_LOG) << "[insert break]" << selection;
1767     printEnclosingBlockTree(enclosingBlock);
1768 #endif
1769 
1770     NodeImpl *nodeToInsert = breakNode;
1771     // Handle the case where there is a typing style.
1772     if (document()->part()->editor()->typingStyle()) {
1773         int exceptionCode = 0;
1774         ElementImpl *styleElement = createTypingStyleElement();
1775         styleElement->appendChild(breakNode, exceptionCode);
1776         assert(exceptionCode == 0);
1777         nodeToInsert = styleElement;
1778     }
1779 
1780     Position pos(selection.start().equivalentDownstreamPosition());
1781     bool atStart = pos.offset() <= pos.node()->caretMinOffset();
1782     bool atEndOfBlock = pos.isLastRenderedPositionInEditableBlock();
1783 
1784 #ifdef DEBUG_COMMANDS
1785     qCDebug(KHTML_LOG) << "[pos]" << pos << atStart << atEndOfBlock;
1786 #endif
1787 
1788     if (atEndOfBlock) {
1789 #ifdef DEBUG_COMMANDS
1790         qCDebug(KHTML_LOG) << "input newline case 1";
1791 #endif
1792         // Insert an "extra" BR at the end of the block. This makes the "real" BR we want
1793         // to insert appear in the rendering without any significant side effects (and no
1794         // real worries either since you can't arrow past this extra one.
1795         insertNodeAfterPosition(nodeToInsert, pos);
1796         exceptionCode = 0;
1797         ElementImpl *extraBreakNode = document()->createHTMLElement("BR");
1798 //         assert(exceptionCode == 0);
1799         insertNodeAfter(extraBreakNode, nodeToInsert);
1800         setEndingSelection(Position(extraBreakNode, 0));
1801     } else if (atStart) {
1802 #ifdef DEBUG_COMMANDS
1803         qCDebug(KHTML_LOG) << "input newline case 2";
1804 #endif
1805         // Insert node, but place the caret into index 0 of the downstream
1806         // position. This will make the caret appear after the break, and as we know
1807         // there is content at that location, this is OK.
1808         insertNodeBeforePosition(nodeToInsert, pos);
1809         setEndingSelection(Position(pos.node(), 0));
1810     } else {
1811         // Split a text node
1812         // FIXME it's possible that we create empty text node now if we're at the end of text
1813         // maybe we should handle this case specially and not create it
1814 #ifdef DEBUG_COMMANDS
1815         qCDebug(KHTML_LOG) << "input newline case 3";
1816 #endif
1817         assert(pos.node()->isTextNode());
1818         TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1819         TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
1820         deleteText(textNode, 0, selection.start().offset());
1821         insertNodeBefore(textBeforeNode, textNode);
1822         insertNodeBefore(nodeToInsert, textNode);
1823         setEndingSelection(Position(textNode, 0));
1824     }
1825 }
1826 
1827 //------------------------------------------------------------------------------------------
1828 // InputTextCommandImpl
1829 
InputTextCommandImpl(DocumentImpl * document)1830 InputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document)
1831     : CompositeEditCommandImpl(document), m_charactersAdded(0)
1832 {
1833 }
1834 
~InputTextCommandImpl()1835 InputTextCommandImpl::~InputTextCommandImpl()
1836 {
1837 }
1838 
doApply()1839 void InputTextCommandImpl::doApply()
1840 {
1841 }
1842 
input(const DOMString & text)1843 void InputTextCommandImpl::input(const DOMString &text)
1844 {
1845     execute(text);
1846 }
1847 
deleteCharacter()1848 void InputTextCommandImpl::deleteCharacter()
1849 {
1850     assert(state() == Applied);
1851 
1852     Selection selection = endingSelection();
1853 
1854     if (!selection.start().node()->isTextNode()) {
1855         return;
1856     }
1857 
1858     int exceptionCode = 0;
1859     int offset = selection.start().offset() - 1;
1860     if (offset >= selection.start().node()->caretMinOffset()) {
1861         TextImpl *textNode = static_cast<TextImpl *>(selection.start().node());
1862         textNode->deleteData(offset, 1, exceptionCode);
1863         assert(exceptionCode == 0);
1864         selection = Selection(Position(textNode, offset));
1865         setEndingSelection(selection);
1866         m_charactersAdded--;
1867     }
1868 }
1869 
prepareForTextInsertion(bool adjustDownstream)1870 Position InputTextCommandImpl::prepareForTextInsertion(bool adjustDownstream)
1871 {
1872     // Prepare for text input by looking at the current position.
1873     // It may be necessary to insert a text node to receive characters.
1874     Selection selection = endingSelection();
1875     assert(selection.state() == Selection::CARET);
1876 
1877 #ifdef DEBUG_COMMANDS
1878     qCDebug(KHTML_LOG) << "[prepare selection]" << selection;
1879 #endif
1880 
1881     Position pos = selection.start();
1882     if (adjustDownstream) {
1883         pos = pos.equivalentDownstreamPosition();
1884     } else {
1885         pos = pos.equivalentUpstreamPosition();
1886     }
1887 
1888 #ifdef DEBUG_COMMANDS
1889     qCDebug(KHTML_LOG) << "[prepare position]" << pos;
1890 #endif
1891 
1892     if (!pos.node()->isTextNode()) {
1893         NodeImpl *textNode = document()->createEditingTextNode("");
1894         NodeImpl *nodeToInsert = textNode;
1895         if (document()->part()->editor()->typingStyle()) {
1896             int exceptionCode = 0;
1897             ElementImpl *styleElement = createTypingStyleElement();
1898             styleElement->appendChild(textNode, exceptionCode);
1899             assert(exceptionCode == 0);
1900             nodeToInsert = styleElement;
1901         }
1902 
1903         // Now insert the node in the right place
1904         if (pos.node()->isEditableBlock()) {
1905             qCDebug(KHTML_LOG) << "prepareForTextInsertion case 1";
1906             appendNode(pos.node(), nodeToInsert);
1907         } else if (pos.node()->id() == ID_BR && pos.offset() == 1) {
1908             qCDebug(KHTML_LOG) << "prepareForTextInsertion case 2";
1909             insertNodeAfter(nodeToInsert, pos.node());
1910         } else if (pos.node()->caretMinOffset() == pos.offset()) {
1911             qCDebug(KHTML_LOG) << "prepareForTextInsertion case 3";
1912             insertNodeBefore(nodeToInsert, pos.node());
1913         } else if (pos.node()->caretMaxOffset() == pos.offset()) {
1914             qCDebug(KHTML_LOG) << "prepareForTextInsertion case 4";
1915             insertNodeAfter(nodeToInsert, pos.node());
1916         } else {
1917             assert(false);
1918         }
1919 
1920         pos = Position(textNode, 0);
1921     } else {
1922         // Handle the case where there is a typing style.
1923         if (document()->part()->editor()->typingStyle()) {
1924             if (pos.node()->isTextNode() && pos.offset() > pos.node()->caretMinOffset() && pos.offset() < pos.node()->caretMaxOffset()) {
1925                 // Need to split current text node in order to insert a span.
1926                 TextImpl *text = static_cast<TextImpl *>(pos.node());
1927                 RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, pos.offset());
1928                 applyCommandToComposite(cmd);
1929                 setEndingSelection(Position(cmd->node(), 0));
1930             }
1931 
1932             int exceptionCode = 0;
1933             TextImpl *editingTextNode = document()->createEditingTextNode("");
1934 
1935             ElementImpl *styleElement = createTypingStyleElement();
1936             styleElement->appendChild(editingTextNode, exceptionCode);
1937             assert(exceptionCode == 0);
1938 
1939             NodeImpl *node = endingSelection().start().node();
1940             if (endingSelection().start().isLastRenderedPositionOnLine()) {
1941                 insertNodeAfter(styleElement, node);
1942             } else {
1943                 insertNodeBefore(styleElement, node);
1944             }
1945             pos = Position(editingTextNode, 0);
1946         }
1947     }
1948     return pos;
1949 }
1950 
execute(const DOMString & text)1951 void InputTextCommandImpl::execute(const DOMString &text)
1952 {
1953 #ifdef DEBUG_COMMANDS
1954     qCDebug(KHTML_LOG) << "[execute command]" << text;
1955 #endif
1956     Selection selection = endingSelection();
1957 #ifdef DEBUG_COMMANDS
1958     qCDebug(KHTML_LOG) << "[ending selection]" << selection;
1959 #endif
1960     bool adjustDownstream = selection.start().isFirstRenderedPositionOnLine();
1961 #ifdef DEBUG_COMMANDS
1962     qCDebug(KHTML_LOG) << "[adjust]" << adjustDownstream;
1963 #endif
1964 
1965 #ifdef DEBUG_COMMANDS
1966     printEnclosingBlockTree(selection.start().node());
1967 #endif
1968 
1969     // Delete the current selection, or collapse whitespace, as needed
1970     if (selection.state() == Selection::RANGE) {
1971         deleteSelection();
1972     } else {
1973         deleteCollapsibleWhitespace();
1974     }
1975 
1976 #ifdef DEBUG_COMMANDS
1977     qCDebug(KHTML_LOG) << "[after collapsible whitespace deletion]";
1978     printEnclosingBlockTree(selection.start().node());
1979 #endif
1980 
1981     // EDIT FIXME: Need to take typing style from upstream text, if any.
1982 
1983     // Make sure the document is set up to receive text
1984     Position pos = prepareForTextInsertion(adjustDownstream);
1985 #ifdef DEBUG_COMMANDS
1986     qCDebug(KHTML_LOG) << "[after prepare]" << pos;
1987 #endif
1988 
1989     TextImpl *textNode = static_cast<TextImpl *>(pos.node());
1990     long offset = pos.offset();
1991 
1992 #ifdef DEBUG_COMMANDS
1993     qCDebug(KHTML_LOG) << "[insert at]" << textNode << offset;
1994 #endif
1995 
1996     // This is a temporary implementation for inserting adjoining spaces
1997     // into a document. We are working on a CSS-related whitespace solution
1998     // that will replace this some day.
1999     if (isWS(text)) {
2000         insertSpace(textNode, offset);
2001     } else {
2002         const DOMString &existingText = textNode->data();
2003         if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isWS(existingText[offset - 2])) {
2004             // DOM looks like this:
2005             // character nbsp caret
2006             // As we are about to insert a non-whitespace character at the caret
2007             // convert the nbsp to a regular space.
2008             // EDIT FIXME: This needs to be improved some day to convert back only
2009             // those nbsp's added by the editor to make rendering come out right.
2010             replaceText(textNode, offset - 1, 1, " ");
2011         }
2012         insertText(textNode, offset, text);
2013     }
2014     setEndingSelection(Position(textNode, offset + text.length()));
2015     m_charactersAdded += text.length();
2016 }
2017 
insertSpace(TextImpl * textNode,unsigned long offset)2018 void InputTextCommandImpl::insertSpace(TextImpl *textNode, unsigned long offset)
2019 {
2020     assert(textNode);
2021 
2022     DOMString text(textNode->data());
2023 
2024     // count up all spaces and newlines in front of the caret
2025     // delete all collapsed ones
2026     // this will work out OK since the offset we have been passed has been upstream-ized
2027     int count = 0;
2028     for (unsigned int i = offset; i < text.length(); i++) {
2029         if (isWS(text[i])) {
2030             count++;
2031         } else {
2032             break;
2033         }
2034     }
2035     if (count > 0) {
2036         // By checking the character at the downstream position, we can
2037         // check if there is a rendered WS at the caret
2038         Position pos(textNode, offset);
2039         Position downstream = pos.equivalentDownstreamPosition();
2040         if (downstream.offset() < (long)text.length() && isWS(text[downstream.offset()])) {
2041             count--;    // leave this WS in
2042         }
2043         if (count > 0) {
2044             deleteText(textNode, offset, count);
2045         }
2046     }
2047 
2048     if (offset > 0 && offset <= text.length() - 1 && !isWS(text[offset]) && !isWS(text[offset - 1])) {
2049         // insert a "regular" space
2050         insertText(textNode, offset, " ");
2051         return;
2052     }
2053 
2054     if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
2055         // DOM looks like this:
2056         // nbsp nbsp caret
2057         // insert a space between the two nbsps
2058         insertText(textNode, offset - 1, " ");
2059         return;
2060     }
2061 
2062     // insert an nbsp
2063     insertText(textNode, offset, nonBreakingSpaceString());
2064 }
2065 
2066 //------------------------------------------------------------------------------------------
2067 // InsertNodeBeforeCommandImpl
2068 
InsertNodeBeforeCommandImpl(DocumentImpl * document,NodeImpl * insertChild,NodeImpl * refChild)2069 InsertNodeBeforeCommandImpl::InsertNodeBeforeCommandImpl(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
2070     : EditCommandImpl(document), m_insertChild(insertChild), m_refChild(refChild)
2071 {
2072     assert(m_insertChild);
2073     m_insertChild->ref();
2074 
2075     assert(m_refChild);
2076     m_refChild->ref();
2077 }
2078 
~InsertNodeBeforeCommandImpl()2079 InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl()
2080 {
2081     if (m_insertChild) {
2082         m_insertChild->deref();
2083     }
2084     if (m_refChild) {
2085         m_refChild->deref();
2086     }
2087 }
2088 
doApply()2089 void InsertNodeBeforeCommandImpl::doApply()
2090 {
2091     assert(m_insertChild);
2092     assert(m_refChild);
2093     assert(m_refChild->parentNode());
2094 
2095     int exceptionCode = 0;
2096     m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
2097     assert(exceptionCode == 0);
2098 }
2099 
doUnapply()2100 void InsertNodeBeforeCommandImpl::doUnapply()
2101 {
2102     assert(m_insertChild);
2103     assert(m_refChild);
2104     assert(m_refChild->parentNode());
2105 
2106     int exceptionCode = 0;
2107     m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
2108     assert(exceptionCode == 0);
2109 }
2110 
2111 //------------------------------------------------------------------------------------------
2112 // InsertTextCommandImpl
2113 
InsertTextCommandImpl(DocumentImpl * document,TextImpl * node,long offset,const DOMString & text)2114 InsertTextCommandImpl::InsertTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
2115     : EditCommandImpl(document), m_node(node), m_offset(offset)
2116 {
2117     assert(m_node);
2118     assert(m_offset >= 0);
2119     assert(text.length() > 0);
2120 
2121     m_node->ref();
2122     m_text = text.copy(); // make a copy to ensure that the string never changes
2123 }
2124 
~InsertTextCommandImpl()2125 InsertTextCommandImpl::~InsertTextCommandImpl()
2126 {
2127     if (m_node) {
2128         m_node->deref();
2129     }
2130 }
2131 
doApply()2132 void InsertTextCommandImpl::doApply()
2133 {
2134     assert(m_node);
2135     assert(!m_text.isEmpty());
2136 
2137     int exceptionCode = 0;
2138     m_node->insertData(m_offset, m_text, exceptionCode);
2139     assert(exceptionCode == 0);
2140 }
2141 
doUnapply()2142 void InsertTextCommandImpl::doUnapply()
2143 {
2144     assert(m_node);
2145     assert(!m_text.isEmpty());
2146 
2147     int exceptionCode = 0;
2148     m_node->deleteData(m_offset, m_text.length(), exceptionCode);
2149     assert(exceptionCode == 0);
2150 }
2151 
2152 //------------------------------------------------------------------------------------------
2153 // JoinTextNodesCommandImpl
2154 
JoinTextNodesCommandImpl(DocumentImpl * document,TextImpl * text1,TextImpl * text2)2155 JoinTextNodesCommandImpl::JoinTextNodesCommandImpl(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
2156     : EditCommandImpl(document), m_text1(text1), m_text2(text2)
2157 {
2158     assert(m_text1);
2159     assert(m_text2);
2160     assert(m_text1->nextSibling() == m_text2);
2161     assert(m_text1->length() > 0);
2162     assert(m_text2->length() > 0);
2163 
2164     m_text1->ref();
2165     m_text2->ref();
2166 }
2167 
~JoinTextNodesCommandImpl()2168 JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl()
2169 {
2170     if (m_text1) {
2171         m_text1->deref();
2172     }
2173     if (m_text2) {
2174         m_text2->deref();
2175     }
2176 }
2177 
doApply()2178 void JoinTextNodesCommandImpl::doApply()
2179 {
2180     assert(m_text1);
2181     assert(m_text2);
2182     assert(m_text1->nextSibling() == m_text2);
2183 
2184     int exceptionCode = 0;
2185     m_text2->insertData(0, m_text1->data(), exceptionCode);
2186     assert(exceptionCode == 0);
2187 
2188     m_text2->parentNode()->removeChild(m_text1, exceptionCode);
2189     assert(exceptionCode == 0);
2190 
2191     m_offset = m_text1->length();
2192 }
2193 
doUnapply()2194 void JoinTextNodesCommandImpl::doUnapply()
2195 {
2196     assert(m_text2);
2197     assert(m_offset > 0);
2198 
2199     int exceptionCode = 0;
2200 
2201     m_text2->deleteData(0, m_offset, exceptionCode);
2202     assert(exceptionCode == 0);
2203 
2204     m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
2205     assert(exceptionCode == 0);
2206 
2207     assert(m_text2->previousSibling()->isTextNode());
2208     assert(m_text2->previousSibling() == m_text1);
2209 }
2210 
2211 //------------------------------------------------------------------------------------------
2212 // ReplaceSelectionCommandImpl
2213 
ReplaceSelectionCommandImpl(DocumentImpl * document,DOM::DocumentFragmentImpl * fragment,bool selectReplacement)2214 ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement)
2215     : CompositeEditCommandImpl(document), m_fragment(fragment), m_selectReplacement(selectReplacement)
2216 {
2217 }
2218 
~ReplaceSelectionCommandImpl()2219 ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl()
2220 {
2221 }
2222 
doApply()2223 void ReplaceSelectionCommandImpl::doApply()
2224 {
2225     NodeImpl *firstChild = m_fragment->firstChild();
2226     NodeImpl *lastChild = m_fragment->lastChild();
2227 
2228     Selection selection = endingSelection();
2229 
2230     // Delete the current selection, or collapse whitespace, as needed
2231     if (selection.state() == Selection::RANGE) {
2232         deleteSelection();
2233     } else {
2234         deleteCollapsibleWhitespace();
2235     }
2236 
2237     selection = endingSelection();
2238     assert(!selection.isEmpty());
2239 
2240     if (!firstChild) {
2241         // Pasting something that didn't parse or was empty.
2242         assert(!lastChild);
2243     } else if (firstChild == lastChild && firstChild->isTextNode()) {
2244         // Simple text paste. Treat as if the text were typed.
2245         Position base = selection.base();
2246         inputText(static_cast<TextImpl *>(firstChild)->data());
2247         if (m_selectReplacement) {
2248             setEndingSelection(Selection(base, endingSelection().extent()));
2249         }
2250     } else {
2251         // HTML fragment paste.
2252         NodeImpl *beforeNode = firstChild;
2253         NodeImpl *node = firstChild->nextSibling();
2254 
2255         insertNodeAt(firstChild, selection.start().node(), selection.start().offset());
2256 
2257         // Insert the nodes from the fragment
2258         while (node) {
2259             NodeImpl *next = node->nextSibling();
2260             insertNodeAfter(node, beforeNode);
2261             beforeNode = node;
2262             node = next;
2263         }
2264         assert(beforeNode);
2265 
2266         // Find the last leaf.
2267         NodeImpl *lastLeaf = lastChild;
2268         while (1) {
2269             NodeImpl *nextChild = lastLeaf->lastChild();
2270             if (!nextChild) {
2271                 break;
2272             }
2273             lastLeaf = nextChild;
2274         }
2275 
2276         if (m_selectReplacement) {
2277             // Find the first leaf.
2278             NodeImpl *firstLeaf = firstChild;
2279             while (1) {
2280                 NodeImpl *nextChild = firstLeaf->firstChild();
2281                 if (!nextChild) {
2282                     break;
2283                 }
2284                 firstLeaf = nextChild;
2285             }
2286             // Select what was inserted.
2287             setEndingSelection(Selection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset())));
2288         } else {
2289             // Place the cursor after what was inserted.
2290             setEndingSelection(Position(lastLeaf, lastLeaf->caretMaxOffset()));
2291         }
2292     }
2293 }
2294 
2295 //------------------------------------------------------------------------------------------
2296 // MoveSelectionCommandImpl
2297 
MoveSelectionCommandImpl(DocumentImpl * document,DOM::DocumentFragmentImpl * fragment,DOM::Position & position)2298 MoveSelectionCommandImpl::MoveSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position)
2299     : CompositeEditCommandImpl(document), m_fragment(fragment), m_position(position)
2300 {
2301 }
2302 
~MoveSelectionCommandImpl()2303 MoveSelectionCommandImpl::~MoveSelectionCommandImpl()
2304 {
2305 }
2306 
doApply()2307 void MoveSelectionCommandImpl::doApply()
2308 {
2309     Selection selection = endingSelection();
2310     assert(selection.state() == Selection::RANGE);
2311 
2312     // Update the position otherwise it may become invalid after the selection is deleted.
2313     NodeImpl *positionNode = m_position.node();
2314     long positionOffset = m_position.offset();
2315     Position selectionEnd = selection.end();
2316     long selectionEndOffset = selectionEnd.offset();
2317     if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) {
2318         positionOffset -= selectionEndOffset;
2319         Position selectionStart = selection.start();
2320         if (selectionStart.node() == positionNode) {
2321             positionOffset += selectionStart.offset();
2322         }
2323     }
2324 
2325     deleteSelection();
2326 
2327     setEndingSelection(Position(positionNode, positionOffset));
2328     RefPtr<ReplaceSelectionCommandImpl> cmd = new ReplaceSelectionCommandImpl(document(), m_fragment, true);
2329     applyCommandToComposite(cmd);
2330 }
2331 
2332 //------------------------------------------------------------------------------------------
2333 // RemoveCSSPropertyCommandImpl
2334 
RemoveCSSPropertyCommandImpl(DocumentImpl * document,CSSStyleDeclarationImpl * decl,int property)2335 RemoveCSSPropertyCommandImpl::RemoveCSSPropertyCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property)
2336     : EditCommandImpl(document), m_decl(decl), m_property(property), m_important(false)
2337 {
2338     assert(m_decl);
2339     m_decl->ref();
2340 }
2341 
~RemoveCSSPropertyCommandImpl()2342 RemoveCSSPropertyCommandImpl::~RemoveCSSPropertyCommandImpl()
2343 {
2344     assert(m_decl);
2345     m_decl->deref();
2346 }
2347 
doApply()2348 void RemoveCSSPropertyCommandImpl::doApply()
2349 {
2350     assert(m_decl);
2351 
2352     m_oldValue = m_decl->getPropertyValue(m_property);
2353     assert(!m_oldValue.isNull());
2354 
2355     m_important = m_decl->getPropertyPriority(m_property);
2356     m_decl->removeProperty(m_property);
2357 }
2358 
doUnapply()2359 void RemoveCSSPropertyCommandImpl::doUnapply()
2360 {
2361     assert(m_decl);
2362     assert(!m_oldValue.isNull());
2363 
2364     m_decl->setProperty(m_property, m_oldValue, m_important);
2365 }
2366 
2367 //------------------------------------------------------------------------------------------
2368 // RemoveNodeAttributeCommandImpl
2369 
RemoveNodeAttributeCommandImpl(DocumentImpl * document,ElementImpl * element,NodeImpl::Id attribute)2370 RemoveNodeAttributeCommandImpl::RemoveNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute)
2371     : EditCommandImpl(document), m_element(element), m_attribute(attribute)
2372 {
2373     assert(m_element);
2374     m_element->ref();
2375 }
2376 
~RemoveNodeAttributeCommandImpl()2377 RemoveNodeAttributeCommandImpl::~RemoveNodeAttributeCommandImpl()
2378 {
2379     assert(m_element);
2380     m_element->deref();
2381 }
2382 
doApply()2383 void RemoveNodeAttributeCommandImpl::doApply()
2384 {
2385     assert(m_element);
2386 
2387     m_oldValue = m_element->getAttribute(m_attribute);
2388     assert(!m_oldValue.isNull());
2389 
2390     int exceptionCode = 0;
2391     m_element->removeAttribute(m_attribute, exceptionCode);
2392     assert(exceptionCode == 0);
2393 }
2394 
doUnapply()2395 void RemoveNodeAttributeCommandImpl::doUnapply()
2396 {
2397     assert(m_element);
2398     assert(!m_oldValue.isNull());
2399 
2400 //     int exceptionCode = 0;
2401     m_element->setAttribute(m_attribute, m_oldValue.implementation());
2402 //     assert(exceptionCode == 0);
2403 }
2404 
2405 //------------------------------------------------------------------------------------------
2406 // RemoveNodeCommandImpl
2407 
RemoveNodeCommandImpl(DocumentImpl * document,NodeImpl * removeChild)2408 RemoveNodeCommandImpl::RemoveNodeCommandImpl(DocumentImpl *document, NodeImpl *removeChild)
2409     : EditCommandImpl(document), m_parent(nullptr), m_removeChild(removeChild), m_refChild(nullptr)
2410 {
2411     assert(m_removeChild);
2412     m_removeChild->ref();
2413 
2414     m_parent = m_removeChild->parentNode();
2415     assert(m_parent);
2416     m_parent->ref();
2417 
2418     RefPtr<DOM::NodeListImpl> children = m_parent->childNodes();
2419     for (long i = children->length() - 1; i >= 0; --i) {
2420         NodeImpl *node = children->item(i);
2421         if (node == m_removeChild) {
2422             break;
2423         }
2424         m_refChild = node;
2425     }
2426 
2427     if (m_refChild) {
2428         m_refChild->ref();
2429     }
2430 }
2431 
~RemoveNodeCommandImpl()2432 RemoveNodeCommandImpl::~RemoveNodeCommandImpl()
2433 {
2434     if (m_parent) {
2435         m_parent->deref();
2436     }
2437     if (m_removeChild) {
2438         m_removeChild->deref();
2439     }
2440     if (m_refChild) {
2441         m_refChild->deref();
2442     }
2443 }
2444 
doApply()2445 void RemoveNodeCommandImpl::doApply()
2446 {
2447     assert(m_parent);
2448     assert(m_removeChild);
2449 
2450     int exceptionCode = 0;
2451     m_parent->removeChild(m_removeChild, exceptionCode);
2452     assert(exceptionCode == 0);
2453 }
2454 
doUnapply()2455 void RemoveNodeCommandImpl::doUnapply()
2456 {
2457     assert(m_parent);
2458     assert(m_removeChild);
2459 
2460     int exceptionCode = 0;
2461     if (m_refChild) {
2462         m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
2463     } else {
2464         m_parent->appendChild(m_removeChild, exceptionCode);
2465     }
2466     assert(exceptionCode == 0);
2467 }
2468 
2469 //------------------------------------------------------------------------------------------
2470 // RemoveNodeAndPruneCommandImpl
2471 
RemoveNodeAndPruneCommandImpl(DocumentImpl * document,NodeImpl * pruneNode,NodeImpl * stopNode)2472 RemoveNodeAndPruneCommandImpl::RemoveNodeAndPruneCommandImpl(DocumentImpl *document, NodeImpl *pruneNode, NodeImpl *stopNode)
2473     : CompositeEditCommandImpl(document), m_pruneNode(pruneNode), m_stopNode(stopNode)
2474 {
2475     assert(m_pruneNode);
2476     m_pruneNode->ref();
2477     if (m_stopNode) {
2478         m_stopNode->ref();
2479     }
2480 }
2481 
~RemoveNodeAndPruneCommandImpl()2482 RemoveNodeAndPruneCommandImpl::~RemoveNodeAndPruneCommandImpl()
2483 {
2484     m_pruneNode->deref();
2485     if (m_stopNode) {
2486         m_stopNode->deref();
2487     }
2488 }
2489 
doApply()2490 void RemoveNodeAndPruneCommandImpl::doApply()
2491 {
2492     NodeImpl *editableBlock = m_pruneNode->enclosingBlockFlowElement();
2493     NodeImpl *pruneNode = m_pruneNode;
2494     NodeImpl *node = pruneNode->traversePreviousNode();
2495     removeNode(pruneNode);
2496     while (1) {
2497         if (node == m_stopNode || editableBlock != node->enclosingBlockFlowElement() || !shouldPruneNode(node)) {
2498             break;
2499         }
2500         pruneNode = node;
2501         node = node->traversePreviousNode();
2502         removeNode(pruneNode);
2503     }
2504 }
2505 
2506 //------------------------------------------------------------------------------------------
2507 // RemoveNodePreservingChildrenCommandImpl
2508 
RemoveNodePreservingChildrenCommandImpl(DocumentImpl * document,NodeImpl * node)2509 RemoveNodePreservingChildrenCommandImpl::RemoveNodePreservingChildrenCommandImpl(DocumentImpl *document, NodeImpl *node)
2510     : CompositeEditCommandImpl(document), m_node(node)
2511 {
2512     assert(m_node);
2513     m_node->ref();
2514 }
2515 
~RemoveNodePreservingChildrenCommandImpl()2516 RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl()
2517 {
2518     if (m_node) {
2519         m_node->deref();
2520     }
2521 }
2522 
doApply()2523 void RemoveNodePreservingChildrenCommandImpl::doApply()
2524 {
2525     RefPtr<DOM::NodeListImpl> children = node()->childNodes();
2526     const unsigned int length = children->length();
2527     for (unsigned int i = 0; i < length; ++i) {
2528         NodeImpl *child = children->item(0);
2529         removeNode(child);
2530         insertNodeBefore(child, node());
2531     }
2532     removeNode(node());
2533 }
2534 
2535 //------------------------------------------------------------------------------------------
2536 // SetNodeAttributeCommandImpl
2537 
SetNodeAttributeCommandImpl(DocumentImpl * document,ElementImpl * element,NodeImpl::Id attribute,const DOMString & value)2538 SetNodeAttributeCommandImpl::SetNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value)
2539     : EditCommandImpl(document), m_element(element), m_attribute(attribute), m_value(value)
2540 {
2541     assert(m_element);
2542     m_element->ref();
2543     assert(!m_value.isNull());
2544 }
2545 
~SetNodeAttributeCommandImpl()2546 SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl()
2547 {
2548     if (m_element) {
2549         m_element->deref();
2550     }
2551 }
2552 
doApply()2553 void SetNodeAttributeCommandImpl::doApply()
2554 {
2555     assert(m_element);
2556     assert(!m_value.isNull());
2557 
2558 //     int exceptionCode = 0;
2559     m_oldValue = m_element->getAttribute(m_attribute);
2560     m_element->setAttribute(m_attribute, m_value.implementation());
2561 //     assert(exceptionCode == 0);
2562 }
2563 
doUnapply()2564 void SetNodeAttributeCommandImpl::doUnapply()
2565 {
2566     assert(m_element);
2567     assert(!m_oldValue.isNull());
2568 
2569 //     int exceptionCode = 0;
2570     m_element->setAttribute(m_attribute, m_oldValue.implementation());
2571 //     assert(exceptionCode == 0);
2572 }
2573 
2574 //------------------------------------------------------------------------------------------
2575 // SplitTextNodeCommandImpl
2576 
SplitTextNodeCommandImpl(DocumentImpl * document,TextImpl * text,long offset)2577 SplitTextNodeCommandImpl::SplitTextNodeCommandImpl(DocumentImpl *document, TextImpl *text, long offset)
2578     : EditCommandImpl(document), m_text1(nullptr), m_text2(text), m_offset(offset)
2579 {
2580     assert(m_text2);
2581     assert(m_text2->length() > 0);
2582 
2583     m_text2->ref();
2584 }
2585 
~SplitTextNodeCommandImpl()2586 SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl()
2587 {
2588     if (m_text1) {
2589         m_text1->deref();
2590     }
2591     if (m_text2) {
2592         m_text2->deref();
2593     }
2594 }
2595 
doApply()2596 void SplitTextNodeCommandImpl::doApply()
2597 {
2598     assert(m_text2);
2599     assert(m_offset > 0);
2600 
2601     int exceptionCode = 0;
2602 
2603     // EDIT FIXME: This should use better smarts for figuring out which portion
2604     // of the split to copy (based on their comparative sizes). We should also
2605     // just use the DOM's splitText function.
2606 
2607     if (!m_text1) {
2608         // create only if needed.
2609         // if reapplying, this object will already exist.
2610         m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode));
2611         assert(exceptionCode == 0);
2612         assert(m_text1);
2613         m_text1->ref();
2614     }
2615 
2616     m_text2->deleteData(0, m_offset, exceptionCode);
2617     assert(exceptionCode == 0);
2618 
2619     m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
2620     assert(exceptionCode == 0);
2621 
2622     assert(m_text2->previousSibling()->isTextNode());
2623     assert(m_text2->previousSibling() == m_text1);
2624 }
2625 
doUnapply()2626 void SplitTextNodeCommandImpl::doUnapply()
2627 {
2628     assert(m_text1);
2629     assert(m_text2);
2630 
2631     assert(m_text1->nextSibling() == m_text2);
2632 
2633     int exceptionCode = 0;
2634     m_text2->insertData(0, m_text1->data(), exceptionCode);
2635     assert(exceptionCode == 0);
2636 
2637     m_text2->parentNode()->removeChild(m_text1, exceptionCode);
2638     assert(exceptionCode == 0);
2639 
2640     m_offset = m_text1->length();
2641 }
2642 
2643 //------------------------------------------------------------------------------------------
2644 // TypingCommandImpl
2645 
TypingCommandImpl(DocumentImpl * document)2646 TypingCommandImpl::TypingCommandImpl(DocumentImpl *document)
2647     : CompositeEditCommandImpl(document), m_openForMoreTyping(true)
2648 {
2649 }
2650 
~TypingCommandImpl()2651 TypingCommandImpl::~TypingCommandImpl()
2652 {
2653 }
2654 
doApply()2655 void TypingCommandImpl::doApply()
2656 {
2657 }
2658 
typingAddedToOpenCommand()2659 void TypingCommandImpl::typingAddedToOpenCommand()
2660 {
2661     assert(document());
2662     assert(document()->part());
2663     document()->part()->editor()->appliedEditing(this);
2664 }
2665 
insertText(const DOMString & text)2666 void TypingCommandImpl::insertText(const DOMString &text)
2667 {
2668     if (document()->part()->editor()->typingStyle() || m_cmds.count() == 0) {
2669         RefPtr<InputTextCommandImpl> cmd = new InputTextCommandImpl(document());
2670         applyCommandToComposite(cmd);
2671         cmd->input(text);
2672     } else {
2673         EditCommandImpl *lastCommand = m_cmds.last().get();
2674         if (lastCommand->isInputTextCommand()) {
2675             static_cast<InputTextCommandImpl *>(lastCommand)->input(text);
2676         } else {
2677             RefPtr<InputTextCommandImpl> cmd = new InputTextCommandImpl(document());
2678             applyCommandToComposite(cmd);
2679             cmd->input(text);
2680         }
2681     }
2682     typingAddedToOpenCommand();
2683 }
2684 
insertNewline()2685 void TypingCommandImpl::insertNewline()
2686 {
2687     RefPtr<InputNewlineCommandImpl> cmd = new InputNewlineCommandImpl(document());
2688     applyCommandToComposite(cmd);
2689     typingAddedToOpenCommand();
2690 }
2691 
issueCommandForDeleteKey()2692 void TypingCommandImpl::issueCommandForDeleteKey()
2693 {
2694     Selection selectionToDelete = endingSelection();
2695     assert(selectionToDelete.state() != Selection::NONE);
2696 
2697 #ifdef DEBUG_COMMANDS
2698     qCDebug(KHTML_LOG) << "[selection]" << selectionToDelete;
2699 #endif
2700     if (selectionToDelete.state() == Selection::CARET) {
2701 #ifdef DEBUG_COMMANDS
2702         qCDebug(KHTML_LOG) << "[caret selection]";
2703 #endif
2704         Position pos(selectionToDelete.start());
2705         if (pos.inFirstEditableInRootEditableElement() && pos.offset() <= pos.node()->caretMinOffset()) {
2706             // we're at the start of a root editable block...do nothing
2707             return;
2708         }
2709         selectionToDelete = Selection(pos.previousCharacterPosition(), pos);
2710 #ifdef DEBUG_COMMANDS
2711         qCDebug(KHTML_LOG) << "[modified selection]" << selectionToDelete;
2712 #endif
2713     }
2714     deleteSelection(selectionToDelete);
2715     typingAddedToOpenCommand();
2716 }
2717 
deleteKeyPressed()2718 void TypingCommandImpl::deleteKeyPressed()
2719 {
2720 // EDIT FIXME: The ifdef'ed out code below should be re-enabled.
2721 // In order for this to happen, the deleteCharacter case
2722 // needs work. Specifically, the caret-positioning code
2723 // and whitespace-handling code in DeleteSelectionCommandImpl::doApply()
2724 // needs to be factored out so it can be used again here.
2725 // Until that work is done, issueCommandForDeleteKey() does the
2726 // right thing, but less efficiently and with the cost of more
2727 // objects.
2728     issueCommandForDeleteKey();
2729 #if 0
2730     if (m_cmds.count() == 0) {
2731         issueCommandForDeleteKey();
2732     } else {
2733         EditCommand lastCommand = m_cmds.last();
2734         if (lastCommand.commandID() == InputTextCommandID) {
2735             InputTextCommand cmd = static_cast<InputTextCommand &>(lastCommand);
2736             cmd.deleteCharacter();
2737             if (cmd.charactersAdded() == 0) {
2738                 removeCommand(cmd);
2739             }
2740         } else if (lastCommand.commandID() == InputNewlineCommandID) {
2741             lastCommand.unapply();
2742             removeCommand(lastCommand);
2743         } else {
2744             issueCommandForDeleteKey();
2745         }
2746     }
2747 #endif
2748 }
2749 
removeCommand(const PassRefPtr<EditCommandImpl> cmd)2750 void TypingCommandImpl::removeCommand(const PassRefPtr<EditCommandImpl> cmd)
2751 {
2752     // NOTE: If the passed-in command is the last command in the
2753     // composite, we could remove all traces of this typing command
2754     // from the system, including the undo chain. Other editors do
2755     // not do this, but we could.
2756 
2757     m_cmds.removeAll(cmd);
2758     if (m_cmds.count() == 0) {
2759         setEndingSelection(startingSelection());
2760     } else {
2761         setEndingSelection(m_cmds.last()->endingSelection());
2762     }
2763 }
2764 
isOpenForMoreTypingCommand(const EditCommandImpl * command)2765 static bool isOpenForMoreTypingCommand(const EditCommandImpl *command)
2766 {
2767     return command && command->isTypingCommand() &&
2768            static_cast<const TypingCommandImpl *>(command)->openForMoreTyping();
2769 }
2770 
deleteKeyPressed0(DocumentImpl * document)2771 void TypingCommandImpl::deleteKeyPressed0(DocumentImpl *document)
2772 {
2773     //Editor *editor = document->part()->editor();
2774     // FIXME reenable after properly modify selection of the lastEditCommand
2775     // if (isOpenForMoreTypingCommand(lastEditCommand)) {
2776     //     static_cast<TypingCommand &>(lastEditCommand).deleteKeyPressed();
2777     // } else {
2778     RefPtr<TypingCommandImpl> command = new TypingCommandImpl(document);
2779     command->apply();
2780     command->deleteKeyPressed();
2781     // }
2782 }
2783 
insertNewline0(DocumentImpl * document)2784 void TypingCommandImpl::insertNewline0(DocumentImpl *document)
2785 {
2786     assert(document);
2787     Editor *ed = document->part()->editor();
2788     assert(ed);
2789     EditCommandImpl *lastEditCommand = ed->lastEditCommand().get();
2790     if (isOpenForMoreTypingCommand(lastEditCommand)) {
2791         static_cast<TypingCommandImpl *>(lastEditCommand)->insertNewline();
2792     } else {
2793         RefPtr<TypingCommandImpl> command = new TypingCommandImpl(document);
2794         command->apply();
2795         command->insertNewline();
2796     }
2797 }
2798 
insertText0(DocumentImpl * document,const DOMString & text)2799 void TypingCommandImpl::insertText0(DocumentImpl *document, const DOMString &text)
2800 {
2801 #ifdef DEBUG_COMMANDS
2802     qCDebug(KHTML_LOG) << "[insert text]" << text;
2803 #endif
2804     assert(document);
2805     Editor *ed = document->part()->editor();
2806     assert(ed);
2807     EditCommandImpl *lastEditCommand = ed->lastEditCommand().get();
2808     if (isOpenForMoreTypingCommand(lastEditCommand)) {
2809         static_cast<TypingCommandImpl *>(lastEditCommand)->insertText(text);
2810     } else {
2811         RefPtr<TypingCommandImpl> command = new TypingCommandImpl(document);
2812         command->apply();
2813         command->insertText(text);
2814     }
2815 }
2816 
2817 //------------------------------------------------------------------------------------------
2818 // InsertListCommandImpl
2819 
InsertListCommandImpl(DocumentImpl * document,Type type)2820 InsertListCommandImpl::InsertListCommandImpl(DocumentImpl *document, Type type)
2821     : CompositeEditCommandImpl(document), m_listType(type)
2822 {
2823 }
2824 
~InsertListCommandImpl()2825 InsertListCommandImpl::~InsertListCommandImpl()
2826 {
2827 }
2828 
doApply()2829 void InsertListCommandImpl::doApply()
2830 {
2831 #ifdef DEBUG_COMMANDS
2832     qCDebug(KHTML_LOG) << "[make current selection/paragraph a list]" << endingSelection();
2833 #endif
2834     Position start = endingSelection().start();
2835     Position end = endingSelection().end();
2836     ElementImpl *startBlock = start.node()->enclosingBlockFlowElement();
2837     ElementImpl *endBlock = end.node()->enclosingBlockFlowElement();
2838 #ifdef DEBUG_COMMANDS
2839     qCDebug(KHTML_LOG) << "[start:end blocks]" << startBlock << endBlock;
2840     printEnclosingBlockTree(start.node());
2841 #endif
2842     if (startBlock == endBlock) {
2843         if (startBlock->id() == ID_LI) {
2844             // we already have a list item, remove it then
2845 #ifdef DEBUG_COMMANDS
2846             qCDebug(KHTML_LOG) << "[remove list item]";
2847 #endif
2848             NodeImpl *listBlock = startBlock->parent(); // it's either <ol> or <ul>
2849             // we need to properly split or even remove the list leaving 2 lists:
2850             // [listBlock->firstChild(), startBlock) and (startBlock, listBlock->lastChild()]
2851             if (listBlock->firstChild() == listBlock->lastChild() && listBlock->firstChild() == startBlock) {
2852                 // get rid of list completely
2853 #ifdef DEBUG_COMMANDS
2854                 qCDebug(KHTML_LOG) << "[remove list completely]";
2855 #endif
2856                 removeNodePreservingChildren(listBlock);
2857                 removeNodePreservingChildren(startBlock);
2858             } else if (!startBlock->previousSibling()) {
2859                 // move nodes from this list item before the list
2860                 NodeImpl *nextSibling;
2861                 for (NodeImpl *node = startBlock->firstChild(); node; node = nextSibling) {
2862                     nextSibling = node->nextSibling();
2863                     removeNode(node);
2864                     insertNodeBefore(node, listBlock);
2865                 }
2866                 removeNode(startBlock);
2867             } else if (!startBlock->nextSibling()) {
2868                 // move nodes from this list item after the list
2869                 NodeImpl *nextSibling;
2870                 for (NodeImpl *node = startBlock->lastChild(); node; node = nextSibling) {
2871                     nextSibling = node->previousSibling();
2872                     removeNode(node);
2873                     insertNodeAfter(node, listBlock);
2874                 }
2875                 removeNode(startBlock);
2876             } else {
2877                 // split list into 2 and nodes from this list item goes between lists
2878                 WTF::PassRefPtr<NodeImpl> newListBlock = listBlock->cloneNode(false);
2879                 insertNodeAfter(newListBlock.get(), listBlock);
2880                 NodeImpl *node, *nextSibling;
2881                 for (node = startBlock->nextSibling(); node; node = nextSibling) {
2882                     nextSibling = node->nextSibling();
2883                     removeNode(node);
2884                     appendNode(newListBlock.get(), node);
2885                 }
2886                 for (node = startBlock->firstChild(); node; node = nextSibling) {
2887                     nextSibling = node->nextSibling();
2888                     removeNode(node);
2889                     insertNodeBefore(node, newListBlock.get());
2890                 }
2891                 removeNode(startBlock);
2892             }
2893         } else {
2894             ElementImpl *ol = document()->createHTMLElement(m_listType == OrderedList ? "OL" : "UL");
2895             ElementImpl *li = document()->createHTMLElement("LI");
2896             appendNode(ol, li);
2897             NodeImpl *nextNode;
2898             for (NodeImpl *node = startBlock->firstChild(); node; node = nextNode) {
2899 #ifdef DEBUG_COMMANDS
2900                 qCDebug(KHTML_LOG) << "[reattach node]" << node;
2901 #endif
2902                 nextNode = node->nextSibling();
2903                 removeNode(node);
2904                 appendNode(li, node);
2905             }
2906             appendNode(startBlock, ol);
2907         }
2908     } else {
2909 #ifdef DEBUG_COMMANDS
2910         qCDebug(KHTML_LOG) << "[different blocks are not supported yet]";
2911 #endif
2912     }
2913 }
2914 
insertList(DocumentImpl * document,Type type)2915 void InsertListCommandImpl::insertList(DocumentImpl *document, Type type)
2916 {
2917     RefPtr<InsertListCommandImpl> insertCommand = new InsertListCommandImpl(document, type);
2918     insertCommand->apply();
2919 }
2920 
2921 //------------------------------------------------------------------------------------------
2922 
2923 //------------------------------------------------------------------------------------------
2924 // IndentOutdentCommandImpl
2925 
IndentOutdentCommandImpl(DocumentImpl * document,Type type)2926 IndentOutdentCommandImpl::IndentOutdentCommandImpl(DocumentImpl *document, Type type)
2927     : CompositeEditCommandImpl(document), m_commandType(type)
2928 {
2929 }
2930 
~IndentOutdentCommandImpl()2931 IndentOutdentCommandImpl::~IndentOutdentCommandImpl()
2932 {
2933 }
2934 
indent()2935 void IndentOutdentCommandImpl::indent()
2936 {
2937     Selection selection = endingSelection();
2938 #ifdef DEBUG_COMMANDS
2939     qCDebug(KHTML_LOG) << "[indent selection]" << selection;
2940 #endif
2941     NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement();
2942     NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement();
2943 
2944     if (startBlock == endBlock) {
2945         // check if selection is the list, but not fully covered
2946         if (startBlock->id() == ID_LI && (startBlock->previousSibling() || startBlock->nextSibling())) {
2947 #ifdef DEBUG_COMMANDS
2948             qCDebug(KHTML_LOG) << "[modify list]";
2949 #endif
2950             RefPtr<NodeImpl> newList = startBlock->parent()->cloneNode(false);
2951             insertNodeAfter(newList.get(), startBlock);
2952             removeNode(startBlock);
2953             appendNode(newList.get(), startBlock);
2954         } else {
2955             NodeImpl *blockquoteElement = document()->createHTMLElement("blockquote");
2956             if (startBlock->id() == ID_LI) {
2957                 startBlock = startBlock->parent();
2958                 NodeImpl *parent = startBlock->parent();
2959                 removeNode(startBlock);
2960                 appendNode(parent, blockquoteElement);
2961                 appendNode(blockquoteElement, startBlock);
2962             } else {
2963                 NodeImpl *parent = startBlock->parent();
2964                 removeNode(startBlock);
2965                 appendNode(parent, blockquoteElement);
2966                 appendNode(blockquoteElement, startBlock);
2967             }
2968         }
2969     } else {
2970         if (startBlock->id() == ID_LI && endBlock->id() == ID_LI && startBlock->parent() == endBlock->parent()) {
2971 #ifdef DEBUG_COMMANDS
2972             qCDebug(KHTML_LOG) << "[indent some items inside list]";
2973 #endif
2974             RefPtr<NodeImpl> nestedList = startBlock->parent()->cloneNode(false);
2975             insertNodeBefore(nestedList.get(), startBlock);
2976             NodeImpl *nextNode = nullptr;
2977             for (NodeImpl *node = startBlock;; node = nextNode) {
2978                 nextNode = node->nextSibling();
2979                 removeNode(node);
2980                 appendNode(nestedList.get(), node);
2981                 if (node == endBlock) {
2982                     break;
2983                 }
2984             }
2985         } else {
2986 #ifdef DEBUG_COMMANDS
2987             qCDebug(KHTML_LOG) << "[blocks not from one list are not supported yet]";
2988 #endif
2989         }
2990     }
2991 }
2992 
hasPreviousListItem(NodeImpl * node)2993 static bool hasPreviousListItem(NodeImpl *node)
2994 {
2995     while (node) {
2996         node = node->previousSibling();
2997         if (node && node->id() == ID_LI) {
2998             return true;
2999         }
3000     }
3001     return false;
3002 }
3003 
hasNextListItem(NodeImpl * node)3004 static bool hasNextListItem(NodeImpl *node)
3005 {
3006     while (node) {
3007         node = node->nextSibling();
3008         if (node && node->id() == ID_LI) {
3009             return true;
3010         }
3011     }
3012     return false;
3013 }
3014 
outdent()3015 void IndentOutdentCommandImpl::outdent()
3016 {
3017     Selection selection = endingSelection();
3018 #ifdef DEBUG_COMMANDS
3019     qCDebug(KHTML_LOG) << "[indent selection]" << selection;
3020 #endif
3021     NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement();
3022     NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement();
3023 
3024     if (startBlock->id() == ID_LI && endBlock->id() == ID_LI && startBlock->parent() == endBlock->parent()) {
3025 #ifdef DEBUG_COMMANDS
3026         qCDebug(KHTML_LOG) << "[list items selected]";
3027 #endif
3028         bool firstItemSelected = !hasPreviousListItem(startBlock);
3029         bool lastItemSelected = !hasNextListItem(endBlock);
3030         bool listFullySelected = firstItemSelected && lastItemSelected;
3031 
3032 #ifdef DEBUG_COMMANDS
3033         qCDebug(KHTML_LOG) << "[first/last item selected]" << firstItemSelected << lastItemSelected;
3034 #endif
3035 
3036         NodeImpl *listNode = startBlock->parent();
3037         printEnclosingBlockTree(listNode);
3038         bool hasParentList = listNode->parent()->id() == ID_OL || listNode->parent()->id() == ID_UL;
3039 
3040         if (!firstItemSelected && !lastItemSelected) {
3041             // split the list into 2 and reattach all the nodes before the first selected item to the second list
3042             RefPtr<NodeImpl> clonedList = listNode->cloneNode(false);
3043             NodeImpl *nextNode = nullptr;
3044             for (NodeImpl *node = listNode->firstChild(); node != startBlock; node = nextNode) {
3045                 nextNode = node->nextSibling();
3046                 removeNode(node);
3047                 appendNode(clonedList.get(), node);
3048             }
3049             insertNodeBefore(clonedList.get(), listNode);
3050             // so now the first item selected
3051             firstItemSelected = true;
3052         }
3053 
3054         NodeImpl *nextNode = nullptr;
3055         for (NodeImpl *node = firstItemSelected ? startBlock : endBlock;; node = nextNode) {
3056             nextNode = firstItemSelected ? node->nextSibling() : node->previousSibling();
3057             removeNode(node);
3058             if (firstItemSelected) {
3059                 insertNodeBefore(node, listNode);
3060             } else {
3061                 insertNodeAfter(node, listNode);
3062             }
3063             if (!hasParentList && node->id() == ID_LI) {
3064                 insertNodeAfter(document()->createHTMLElement("BR"), node);
3065                 removeNodePreservingChildren(node);
3066             }
3067             if (node == (firstItemSelected ? endBlock : startBlock)) {
3068                 break;
3069             }
3070         }
3071         if (listFullySelected) {
3072             removeNode(listNode);
3073         }
3074         return;
3075     }
3076 
3077     if (startBlock == endBlock) {
3078         if (startBlock->id() == ID_BLOCKQUOTE) {
3079             removeNodePreservingChildren(startBlock);
3080         } else {
3081 #ifdef DEBUG_COMMANDS
3082             qCDebug(KHTML_LOG) << "[not the list or blockquote]";
3083 #endif
3084         }
3085     } else {
3086 #ifdef DEBUG_COMMANDS
3087         qCDebug(KHTML_LOG) << "[blocks not from one list are not supported yet]";
3088 #endif
3089     }
3090 }
3091 
doApply()3092 void IndentOutdentCommandImpl::doApply()
3093 {
3094     if (m_commandType == Indent) {
3095         indent();
3096     } else {
3097         outdent();
3098     }
3099 }
3100 
3101 //------------------------------------------------------------------------------------------
3102 
3103 } // namespace khtml
3104 
3105