KHtml

htmlediting_impl.cpp
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;
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 
83 static inline bool isNBSP(const QChar &c)
84 {
85  return c == QChar(0xa0);
86 }
87 
88 static inline bool isWS(const QChar &c)
89 {
90  return c.isSpace() && c != QChar(0xa0);
91 }
92 
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 
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 
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 
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 
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 
203 static bool textNodesAreJoinable(TextImpl *text1, TextImpl *text2)
204 {
205  assert(text1);
206  assert(text2);
207 
208  return (text1->nextSibling() == text2);
209 }
210 
211 static DOMString &nonBreakingSpaceString()
212 {
213  static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
214  return nonBreakingSpaceString;
215 }
216 
217 static DOMString &styleSpanClassString()
218 {
219  static DOMString styleSpanClassString = "khtml-style-span";
220  return styleSpanClassString;
221 }
222 
223 //------------------------------------------------------------------------------------------
224 // EditCommandImpl
225 
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 
236 EditCommandImpl::~EditCommandImpl()
237 {
238  m_document->deref();
239 }
240 
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 
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 
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 
286 void EditCommandImpl::doReapply()
287 {
288  doApply();
289 }
290 
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 
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 
311 EditCommandImpl *EditCommandImpl::parent() const
312 {
313  return m_parent.get();
314 }
315 
316 void EditCommandImpl::setParent(EditCommandImpl *cmd)
317 {
318  m_parent = cmd;
319 }
320 
321 //------------------------------------------------------------------------------------------
322 // CompositeEditCommandImpl
323 
324 CompositeEditCommandImpl::CompositeEditCommandImpl(DocumentImpl *document)
325  : EditCommandImpl(document)
326 {
327 }
328 
329 CompositeEditCommandImpl::~CompositeEditCommandImpl()
330 {
331 }
332 
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 
346 void CompositeEditCommandImpl::doReapply()
347 {
348  if (m_cmds.count() == 0) {
349  return;
350  }
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 //
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 
371 void CompositeEditCommandImpl::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
372 {
373  RefPtr<InsertNodeBeforeCommandImpl> cmd = new InsertNodeBeforeCommandImpl(document(), insertChild, refChild);
374  applyCommandToComposite(cmd);
375 }
376 
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 
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 
409 void CompositeEditCommandImpl::appendNode(NodeImpl *parent, NodeImpl *appendChild)
410 {
411  RefPtr<AppendNodeCommandImpl> cmd = new AppendNodeCommandImpl(document(), parent, appendChild);
412  applyCommandToComposite(cmd);
413 }
414 
415 void CompositeEditCommandImpl::removeNode(NodeImpl *removeChild)
416 {
417  RefPtr<RemoveNodeCommandImpl> cmd = new RemoveNodeCommandImpl(document(), removeChild);
418  applyCommandToComposite(cmd);
419 }
420 
421 void CompositeEditCommandImpl::removeNodeAndPrune(NodeImpl *pruneNode, NodeImpl *stopNode)
422 {
423  RefPtr<RemoveNodeAndPruneCommandImpl> cmd = new RemoveNodeAndPruneCommandImpl(document(), pruneNode, stopNode);
424  applyCommandToComposite(cmd);
425 }
426 
427 void CompositeEditCommandImpl::removeNodePreservingChildren(NodeImpl *removeChild)
428 {
429  RefPtr<RemoveNodePreservingChildrenCommandImpl> cmd = new RemoveNodePreservingChildrenCommandImpl(document(), removeChild);
430  applyCommandToComposite(cmd);
431 }
432 
433 void CompositeEditCommandImpl::splitTextNode(TextImpl *text, long offset)
434 {
435  RefPtr<SplitTextNodeCommandImpl> cmd = new SplitTextNodeCommandImpl(document(), text, offset);
436  applyCommandToComposite(cmd);
437 }
438 
439 void CompositeEditCommandImpl::joinTextNodes(TextImpl *text1, TextImpl *text2)
440 {
441  RefPtr<JoinTextNodesCommandImpl> cmd = new JoinTextNodesCommandImpl(document(), text1, text2);
442  applyCommandToComposite(cmd);
443 }
444 
445 void CompositeEditCommandImpl::inputText(const DOMString &text)
446 {
447  RefPtr<InputTextCommandImpl> cmd = new InputTextCommandImpl(document());
448  applyCommandToComposite(cmd);
449  cmd->input(text);
450 }
451 
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 
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 
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 
472 void CompositeEditCommandImpl::deleteSelection()
473 {
474  if (endingSelection().state() == Selection::RANGE) {
475  RefPtr<DeleteSelectionCommandImpl> cmd = new DeleteSelectionCommandImpl(document());
476  applyCommandToComposite(cmd);
477  }
478 }
479 
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 
488 void CompositeEditCommandImpl::deleteCollapsibleWhitespace()
489 {
490  RefPtr<DeleteCollapsibleWhitespaceCommandImpl> cmd = new DeleteCollapsibleWhitespaceCommandImpl(document());
491  applyCommandToComposite(cmd);
492 }
493 
494 void CompositeEditCommandImpl::deleteCollapsibleWhitespace(const Selection &selection)
495 {
496  RefPtr<DeleteCollapsibleWhitespaceCommandImpl> cmd = new DeleteCollapsibleWhitespaceCommandImpl(document(), selection);
497  applyCommandToComposite(cmd);
498 }
499 
500 void CompositeEditCommandImpl::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
501 {
502  RefPtr<RemoveCSSPropertyCommandImpl> cmd = new RemoveCSSPropertyCommandImpl(document(), decl, property);
503  applyCommandToComposite(cmd);
504 }
505 
506 void CompositeEditCommandImpl::removeNodeAttribute(ElementImpl *element, int attribute)
507 {
508  RefPtr<RemoveNodeAttributeCommandImpl> cmd = new RemoveNodeAttributeCommandImpl(document(), element, attribute);
509  applyCommandToComposite(cmd);
510 }
511 
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 
518 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 
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 
544 AppendNodeCommandImpl::~AppendNodeCommandImpl()
545 {
546  if (m_parentNode) {
547  m_parentNode->deref();
548  }
549  if (m_appendChild) {
550  m_appendChild->deref();
551  }
552 }
553 
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 
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 
578 ApplyStyleCommandImpl::ApplyStyleCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *style)
579  : CompositeEditCommandImpl(document), m_style(style)
580 {
581  assert(m_style);
582  m_style->ref();
583 }
584 
585 ApplyStyleCommandImpl::~ApplyStyleCommandImpl()
586 {
587  assert(m_style);
588  m_style->deref();
589 }
590 
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 
621 static void applyStyleChangeOnTheNode(ElementImpl *element, CSSStyleDeclarationImpl *style)
622 {
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 
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 
734 bool ApplyStyleCommandImpl::isHTMLStyleNode(HTMLElementImpl *elem)
735 {
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 
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 
766 void ApplyStyleCommandImpl::removeCSSStyle(HTMLElementImpl *elem)
767 {
768  assert(elem);
769 
770  CSSStyleDeclarationImpl *decl = elem->inlineStyleDecls();
771  if (!decl) {
772  return;
773  }
774 
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 
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 
815 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 
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 
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 
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 
890 static bool /*ApplyStyleCommandImpl::*/checkIfNewStylingNeeded(ElementImpl *element, CSSStyleDeclarationImpl *style)
891 {
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 
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 
933 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 
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 
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 
1032 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document)
1033  : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_hasSelectionToCollapse(false)
1034 {
1035 }
1036 
1037 DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document, const Selection &selection)
1038  : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_selectionToCollapse(selection), m_hasSelectionToCollapse(true)
1039 {
1040 }
1041 
1042 DeleteCollapsibleWhitespaceCommandImpl::~DeleteCollapsibleWhitespaceCommandImpl()
1043 {
1044 }
1045 
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 
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 
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 
1217 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document)
1218  : CompositeEditCommandImpl(document), m_hasSelectionToDelete(false)
1219 {
1220 }
1221 
1222 DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document, const Selection &selection)
1223  : CompositeEditCommandImpl(document), m_selectionToDelete(selection), m_hasSelectionToDelete(true)
1224 {
1225 }
1226 
1227 DeleteSelectionCommandImpl::~DeleteSelectionCommandImpl()
1228 {
1229 }
1230 
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 
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 
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 
1331 void DeleteSelectionCommandImpl::deleteContentBeforeOffset(NodeImpl *node, int offset)
1332 {
1333  deleteContentInsideNode(node, 0, offset);
1334 }
1335 
1336 void DeleteSelectionCommandImpl::deleteContentAfterOffset(NodeImpl *node, int offset)
1337 {
1338  if (node->isTextNode()) {
1339  deleteContentInsideNode(node, offset, node->maxOffset());
1340  }
1341 }
1342 
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 
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 
1621 DeleteTextCommandImpl::~DeleteTextCommandImpl()
1622 {
1623  if (m_node) {
1624  m_node->deref();
1625  }
1626 }
1627 
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 
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 
1653 InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document)
1654  : CompositeEditCommandImpl(document)
1655 {
1656 }
1657 
1658 InputNewlineCommandImpl::~InputNewlineCommandImpl()
1659 {
1660 }
1661 
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 
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 
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 
1830 InputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document)
1831  : CompositeEditCommandImpl(document), m_charactersAdded(0)
1832 {
1833 }
1834 
1835 InputTextCommandImpl::~InputTextCommandImpl()
1836 {
1837 }
1838 
1839 void InputTextCommandImpl::doApply()
1840 {
1841 }
1842 
1843 void InputTextCommandImpl::input(const DOMString &text)
1844 {
1845  execute(text);
1846 }
1847 
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 
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 
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 
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 
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 
2079 InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl()
2080 {
2081  if (m_insertChild) {
2082  m_insertChild->deref();
2083  }
2084  if (m_refChild) {
2085  m_refChild->deref();
2086  }
2087 }
2088 
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 
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 
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 
2125 InsertTextCommandImpl::~InsertTextCommandImpl()
2126 {
2127  if (m_node) {
2128  m_node->deref();
2129  }
2130 }
2131 
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 
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 
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 
2168 JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl()
2169 {
2170  if (m_text1) {
2171  m_text1->deref();
2172  }
2173  if (m_text2) {
2174  m_text2->deref();
2175  }
2176 }
2177 
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 
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 
2214 ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement)
2215  : CompositeEditCommandImpl(document), m_fragment(fragment), m_selectReplacement(selectReplacement)
2216 {
2217 }
2218 
2219 ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl()
2220 {
2221 }
2222 
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 
2298 MoveSelectionCommandImpl::MoveSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position)
2299  : CompositeEditCommandImpl(document), m_fragment(fragment), m_position(position)
2300 {
2301 }
2302 
2303 MoveSelectionCommandImpl::~MoveSelectionCommandImpl()
2304 {
2305 }
2306 
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 
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 
2342 RemoveCSSPropertyCommandImpl::~RemoveCSSPropertyCommandImpl()
2343 {
2344  assert(m_decl);
2345  m_decl->deref();
2346 }
2347 
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 
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 
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 
2377 RemoveNodeAttributeCommandImpl::~RemoveNodeAttributeCommandImpl()
2378 {
2379  assert(m_element);
2380  m_element->deref();
2381 }
2382 
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 
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 
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 
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 
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 
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 
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 
2482 RemoveNodeAndPruneCommandImpl::~RemoveNodeAndPruneCommandImpl()
2483 {
2484  m_pruneNode->deref();
2485  if (m_stopNode) {
2486  m_stopNode->deref();
2487  }
2488 }
2489 
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 
2509 RemoveNodePreservingChildrenCommandImpl::RemoveNodePreservingChildrenCommandImpl(DocumentImpl *document, NodeImpl *node)
2510  : CompositeEditCommandImpl(document), m_node(node)
2511 {
2512  assert(m_node);
2513  m_node->ref();
2514 }
2515 
2516 RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl()
2517 {
2518  if (m_node) {
2519  m_node->deref();
2520  }
2521 }
2522 
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 
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 
2546 SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl()
2547 {
2548  if (m_element) {
2549  m_element->deref();
2550  }
2551 }
2552 
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 
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 
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 
2586 SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl()
2587 {
2588  if (m_text1) {
2589  m_text1->deref();
2590  }
2591  if (m_text2) {
2592  m_text2->deref();
2593  }
2594 }
2595 
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 
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 
2646 TypingCommandImpl::TypingCommandImpl(DocumentImpl *document)
2647  : CompositeEditCommandImpl(document), m_openForMoreTyping(true)
2648 {
2649 }
2650 
2651 TypingCommandImpl::~TypingCommandImpl()
2652 {
2653 }
2654 
2655 void TypingCommandImpl::doApply()
2656 {
2657 }
2658 
2659 void TypingCommandImpl::typingAddedToOpenCommand()
2660 {
2661  assert(document());
2662  assert(document()->part());
2663  document()->part()->editor()->appliedEditing(this);
2664 }
2665 
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 
2685 void TypingCommandImpl::insertNewline()
2686 {
2687  RefPtr<InputNewlineCommandImpl> cmd = new InputNewlineCommandImpl(document());
2688  applyCommandToComposite(cmd);
2689  typingAddedToOpenCommand();
2690 }
2691 
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 
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 
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 
2765 static bool isOpenForMoreTypingCommand(const EditCommandImpl *command)
2766 {
2767  return command && command->isTypingCommand() &&
2768  static_cast<const TypingCommandImpl *>(command)->openForMoreTyping();
2769 }
2770 
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 
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 
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 
2820 InsertListCommandImpl::InsertListCommandImpl(DocumentImpl *document, Type type)
2821  : CompositeEditCommandImpl(document), m_listType(type)
2822 {
2823 }
2824 
2825 InsertListCommandImpl::~InsertListCommandImpl()
2826 {
2827 }
2828 
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 
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 
2926 IndentOutdentCommandImpl::IndentOutdentCommandImpl(DocumentImpl *document, Type type)
2927  : CompositeEditCommandImpl(document), m_commandType(type)
2928 {
2929 }
2930 
2931 IndentOutdentCommandImpl::~IndentOutdentCommandImpl()
2932 {
2933 }
2934 
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 
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 
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 
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 
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 
Node lastChild() const
The last child of this node.
Definition: dom_node.cpp:274
The Node interface is the primary datatype for the entire Document Object Model.
Definition: dom_node.h:278
This class resembles the editing API when the associated khtml document is editable (in design mode)...
Definition: editor.h:63
CSSStyleDeclaration style()
Introduced in DOM Level 2 This method is from the CSSStyleDeclaration interface.
Node previousSibling() const
The node immediately preceding this node.
Definition: dom_node.cpp:282
Node appendChild(const Node &newChild)
Adds the node newChild to the end of the list of children of this node.
Definition: dom_node.cpp:354
This file is part of the HTML rendering engine for KDE.
MESSAGECORE_EXPORT KMime::Content * next(KMime::Content *node, bool allowChildren=true)
KIOCORE_EXPORT DeleteJob * del(const QUrl &src, JobFlags flags=DefaultFlags)
QVector< V > values(const QMultiHash< K, V > &c)
bool isSpace() const const
Node parentNode() const
The parent of this node.
Definition: dom_node.cpp:250
DOM::Editor * editor() const
Returns the instance of the attached html editor interface.
The CSSPrimitiveValue interface represents a single CSS value .
Definition: css_value.h:367
This class implements the basic string we use in the DOM.
Definition: dom_string.h:44
Node removeChild(const Node &oldChild)
Removes the child node indicated by oldChild from the list of children, and returns it...
Definition: dom_node.cpp:340
void appliedEditing(khtml::EditCommandImpl *)
Called when editing has been applied.
Definition: editor.cpp:424
Node firstChild() const
The first child of this node.
Definition: dom_node.cpp:266
const QList< QKeySequence > & end()
WTF::PassRefPtr< khtml::EditCommandImpl > lastEditCommand() const
Returns the most recent edit command applied.
Definition: editor.cpp:419
Node item(unsigned long index) const
This method retrieves a node specified by ordinal index.
Definition: html_misc.cpp:179
QFuture< void > map(Sequence &sequence, MapFunctor function)
Node nextSibling() const
The node immediately following this node.
Definition: dom_node.cpp:290
HTMLCollection children() const
Retrieves a collection of nodes that are direct descendants of this node.
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 16 2021 22:47:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.