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