KHtml

dom_selection.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 "dom_selection.h"
27 
28 #include "khtml_part.h"
29 #include "khtmlpart_p.h"
30 #include "khtmlview.h"
31 #include "dom/dom2_range.h"
32 #include "dom/dom_node.h"
33 #include "dom/dom_string.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_positioniterator.h"
39 #include "xml/dom_elementimpl.h"
40 #include "xml/dom_nodeimpl.h"
41 #include "xml/dom_textimpl.h"
42 
43 #include <QEvent>
44 #include <QPainter>
45 #include <QPaintEngine>
46 #include <QRect>
47 
48 #define EDIT_DEBUG 0
49 #define DEBUG_CARET
50 
51 using khtml::EditorContext;
52 using khtml::findWordBoundary;
53 using khtml::InlineTextBox;
55 using khtml::RenderText;
56 using khtml::RenderPosition;
57 
58 namespace DOM
59 {
60 
61 static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset);
62 static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset);
63 static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection);
64 
65 static inline Position &emptyPosition()
66 {
67  static Position EmptyPosition = Position();
68  return EmptyPosition;
69 }
70 
71 Selection::Selection()
72 {
73  init();
74 }
75 
76 Selection::Selection(const Position &pos)
77 {
78  init();
79  assignBaseAndExtent(pos, pos);
80  validate();
81 }
82 
83 Selection::Selection(const Range &r)
84 {
85  const Position start(r.startContainer().handle(), r.startOffset());
86  const Position end(r.endContainer().handle(), r.endOffset());
87 
88  init();
89  assignBaseAndExtent(start, end);
90  validate();
91 }
92 
93 Selection::Selection(const Position &base, const Position &extent)
94 {
95  init();
96  assignBaseAndExtent(base, extent);
97  validate();
98 }
99 
100 Selection::Selection(const Selection &o)
101 {
102  init();
103 
104  assignBaseAndExtent(o.base(), o.extent());
105  assignStartAndEnd(o.start(), o.end());
106 
107  m_state = o.m_state;
108  m_affinity = o.m_affinity;
109 
110  m_baseIsStart = o.m_baseIsStart;
111  m_needsCaretLayout = o.m_needsCaretLayout;
112  m_modifyBiasSet = o.m_modifyBiasSet;
113 
114  // Only copy the coordinates over if the other object
115  // has had a layout, otherwise keep the current
116  // coordinates. This prevents drawing artifacts from
117  // remaining when the caret is painted and then moves,
118  // and the old rectangle needs to be repainted.
119  if (!m_needsCaretLayout) {
120  m_caretX = o.m_caretX;
121  m_caretY = o.m_caretY;
122  m_caretSize = o.m_caretSize;
123  }
124 }
125 
126 void Selection::init()
127 {
128  m_base = m_extent = m_start = m_end = emptyPosition();
129  m_state = NONE;
130  m_caretX = 0;
131  m_caretY = 0;
132  m_caretSize = 0;
133  m_baseIsStart = true;
134  m_needsCaretLayout = true;
135  m_modifyBiasSet = false;
136  m_affinity = DOWNSTREAM;
137 }
138 
139 Selection &Selection::operator=(const Selection &o)
140 {
141  assignBaseAndExtent(o.base(), o.extent());
142  assignStartAndEnd(o.start(), o.end());
143 
144  m_state = o.m_state;
145  m_affinity = o.m_affinity;
146 
147  m_baseIsStart = o.m_baseIsStart;
148  m_needsCaretLayout = o.m_needsCaretLayout;
149  m_modifyBiasSet = o.m_modifyBiasSet;
150 
151  // Only copy the coordinates over if the other object
152  // has had a layout, otherwise keep the current
153  // coordinates. This prevents drawing artifacts from
154  // remaining when the caret is painted and then moves,
155  // and the old rectangle needs to be repainted.
156  if (!m_needsCaretLayout) {
157  m_caretX = o.m_caretX;
158  m_caretY = o.m_caretY;
159  m_caretSize = o.m_caretSize;
160  }
161 
162  return *this;
163 }
164 
165 void Selection::setAffinity(EAffinity affinity)
166 {
167  if (affinity == m_affinity) {
168  return;
169  }
170 
171  m_affinity = affinity;
172  setNeedsLayout();
173 }
174 
175 void Selection::moveTo(const Range &r)
176 {
177  Position start(r.startContainer().handle(), r.startOffset());
178  Position end(r.endContainer().handle(), r.endOffset());
179  moveTo(start, end);
180 }
181 
182 void Selection::moveTo(const Selection &o)
183 {
184  moveTo(o.start(), o.end());
185 }
186 
187 void Selection::moveTo(const Position &pos)
188 {
189  moveTo(pos, pos);
190 }
191 
192 void Selection::moveTo(const Position &base, const Position &extent)
193 {
194 // kdDebug(6200) << "Selection::moveTo: base(" << base.node() << "," << base.offset() << "), extent(" << extent.node() << "," << extent.offset() << ")";
195 #ifdef DEBUG_CARET
196  qCDebug(KHTML_LOG) << *this << base << extent;
197 #endif
198  assignBaseAndExtent(base, extent);
199  validate();
200 }
201 
202 bool Selection::modify(EAlter alter, EDirection dir, ETextGranularity granularity)
203 {
204  Position pos;
205 
206  switch (dir) {
207  // EDIT FIXME: This needs to handle bidi
208  case RIGHT:
209  case FORWARD:
210  if (alter == EXTEND) {
211  if (!m_modifyBiasSet) {
212  m_modifyBiasSet = true;
213  assignBaseAndExtent(start(), end());
214  }
215  switch (granularity) {
216  case CHARACTER:
217  pos = extent().nextCharacterPosition();
218  break;
219  case WORD:
220  pos = extent().nextWordPosition();
221  break;
222  case LINE:
223  pos = extent().nextLinePosition(xPosForVerticalArrowNavigation(EXTENT));
224  break;
225  case PARAGRAPH:
226  // not implemented
227  break;
228  }
229  } else {
230  m_modifyBiasSet = false;
231  switch (granularity) {
232  case CHARACTER:
233  pos = (state() == RANGE) ? end() : extent().nextCharacterPosition();
234  break;
235  case WORD:
236  pos = extent().nextWordPosition();
237  break;
238  case LINE:
239  pos = end().nextLinePosition(xPosForVerticalArrowNavigation(END, state() == RANGE));
240  break;
241  case PARAGRAPH:
242  // not implemented
243  break;
244  }
245  }
246  break;
247  // EDIT FIXME: This needs to handle bidi
248  case LEFT:
249  case BACKWARD:
250  if (alter == EXTEND) {
251  if (!m_modifyBiasSet) {
252  m_modifyBiasSet = true;
253  assignBaseAndExtent(end(), start());
254  }
255  switch (granularity) {
256  case CHARACTER:
257  pos = extent().previousCharacterPosition();
258  break;
259  case WORD:
260  pos = extent().previousWordPosition();
261  break;
262  case LINE:
263  pos = extent().previousLinePosition(xPosForVerticalArrowNavigation(EXTENT));
264  break;
265  case PARAGRAPH:
266  // not implemented
267  break;
268  }
269  } else {
270  m_modifyBiasSet = false;
271  switch (granularity) {
272  case CHARACTER:
273  pos = (state() == RANGE) ? start() : extent().previousCharacterPosition();
274  break;
275  case WORD:
276  pos = extent().previousWordPosition();
277  break;
278  case LINE:
279  pos = start().previousLinePosition(xPosForVerticalArrowNavigation(START, state() == RANGE));
280  break;
281  case PARAGRAPH:
282  // not implemented
283  break;
284  }
285  }
286  break;
287  }
288 
289  if (pos.isEmpty()) {
290  return false;
291  }
292 
293  if (alter == MOVE) {
294  moveTo(pos);
295  } else { // alter == EXTEND
296  setExtent(pos);
297  }
298 
299  return true;
300 }
301 
302 bool Selection::expandUsingGranularity(ETextGranularity granularity)
303 {
304  if (state() == NONE) {
305  return false;
306  }
307 
308  validate(granularity);
309  return true;
310 }
311 
312 int Selection::xPosForVerticalArrowNavigation(EPositionType type, bool recalc) const
313 {
314  int x = 0;
315 
316  if (state() == NONE) {
317  return x;
318  }
319 
320  Position pos;
321  switch (type) {
322  case START:
323  pos = start();
324  break;
325  case END:
326  pos = end();
327  break;
328  case BASE:
329  pos = base();
330  break;
331  case EXTENT:
332  pos = extent();
333  break;
334  case CARETPOS:
335  pos = caretPos();
336  break;
337  }
338 
339  KHTMLPart *part = pos.node()->document()->part();
340  if (!part) {
341  return x;
342  }
343 
344  if (recalc || part->d->editor_context.m_xPosForVerticalArrowNavigation == EditorContext::NoXPosForVerticalArrowNavigation
345  ) {
346  int y, w, h;
347  if (pos.node()->renderer()) {
348  pos.node()->renderer()->caretPos(pos.renderedOffset(), 0, x, y, w, h);
349  }
350  part->d->editor_context.m_xPosForVerticalArrowNavigation = x;
351  } else {
352  x = part->d->editor_context.m_xPosForVerticalArrowNavigation;
353  }
354 
355  return x;
356 }
357 
358 void Selection::clear()
359 {
360  assignBaseAndExtent(emptyPosition(), emptyPosition());
361  validate();
362 }
363 
364 void Selection::collapse()
365 {
366  moveTo(caretPos());
367 }
368 
369 void Selection::setBase(const Position &pos)
370 {
371  assignBase(pos);
372  validate();
373 }
374 
375 void Selection::setExtent(const Position &pos)
376 {
377  assignExtent(pos);
378  validate();
379 }
380 
381 void Selection::setBaseAndExtent(const Position &base, const Position &extent)
382 {
383  assignBaseAndExtent(base, extent);
384  validate();
385 }
386 
387 void Selection::setStart(const Position &pos)
388 {
389  assignStart(pos);
390  validate();
391 }
392 
393 void Selection::setEnd(const Position &pos)
394 {
395  assignEnd(pos);
396  validate();
397 }
398 
399 void Selection::setStartAndEnd(const Position &start, const Position &end)
400 {
401  assignStartAndEnd(start, end);
402  validate();
403 }
404 
405 void Selection::setNeedsLayout(bool flag)
406 {
407  m_needsCaretLayout = flag;
408 }
409 
410 void Selection::getRange(NodeImpl *&st, long &so, NodeImpl *&en, long &eo) const
411 {
412  if (isEmpty()) {
413  st = en = nullptr; so = eo = 0;
414  return;
415  }
416 
417  // Make sure we have an updated layout since this function is called
418  // in the course of running edit commands which modify the DOM.
419  // Failing to call this can result in equivalentXXXPosition calls returning
420  // incorrect results.
421  start().node()->document()->updateLayout();
422 
423  Position s, e;
424  if (state() == CARET) {
425  // If the selection is a caret, move the range start upstream. This helps us match
426  // the conventions of text editors tested, which make style determinations based
427  // on the character before the caret, if any.
428  s = start().equivalentUpstreamPosition().equivalentRangeCompliantPosition();
429  e = s;
430  } else {
431  // If the selection is a range, select the minimum range that encompasses the selection.
432  // Again, this is to match the conventions of text editors tested, which make style
433  // determinations based on the first character of the selection.
434  // For instance, this operation helps to make sure that the "X" selected below is the
435  // only thing selected. The range should not be allowed to "leak" out to the end of the
436  // previous text node, or to the beginning of the next text node, each of which has a
437  // different style.
438  //
439  // On a treasure map, <b>X</b> marks the spot.
440  // ^ selected
441  //
442  assert(state() == RANGE);
443  s = start().equivalentDownstreamPosition();
444  e = end().equivalentUpstreamPosition();
445  if ((s.node() == e.node() && s.offset() > e.offset()) || !nodeIsBeforeNode(s.node(), e.node())) {
446  // Make sure the start is before the end.
447  // The end can wind up before the start if collapsed whitespace is the only thing selected.
448  Position tmp = s;
449  s = e;
450  e = tmp;
451  }
452  s = s.equivalentRangeCompliantPosition();
453  e = e.equivalentRangeCompliantPosition();
454  }
455 
456  st = s.node();
457  so = s.offset();
458  en = e.node();
459  eo = e.offset();
460 }
461 
462 Range Selection::toRange() const
463 {
464  if (isEmpty()) {
465  return Range();
466  }
467 
468  NodeImpl *start, *end;
469  long so, eo;
470  getRange(start, so, end, eo);
471  return Range(Node(start), so, Node(end), eo);
472 }
473 
474 void Selection::layoutCaret()
475 {
476  if (isEmpty() || !caretPos().node()->renderer()) {
477  m_caretX = m_caretY = m_caretSize = 0;
478  } else {
479  // EDIT FIXME: Enhance call to pass along selection
480  // upstream/downstream affinity to get the right position.
481  int w;
482  int offset = RenderPosition::fromDOMPosition(caretPos()).renderedOffset();
483 #ifdef DEBUG_CARET
484  qCDebug(KHTML_LOG) << "[before caretPos()]" << m_caretX;
485 #endif
486  caretPos().node()->renderer()->caretPos(offset, true, m_caretX, m_caretY, w, m_caretSize);
487 #ifdef DEBUG_CARET
488  qCDebug(KHTML_LOG) << "[after caretPos()]" << m_caretX;
489 #endif
490  }
491 
492  m_needsCaretLayout = false;
493 }
494 
495 QRect Selection::getRepaintRect() const
496 {
497  if (m_needsCaretLayout) {
498  const_cast<Selection *>(this)->layoutCaret();
499  }
500 
501  // EDIT FIXME: fudge a bit to make sure we don't leave behind artifacts
502  return QRect(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2);
503 }
504 
505 void Selection::needsCaretRepaint()
506 {
507  if (isEmpty()) {
508  return;
509  }
510 
511  if (!start().node()) {
512  return;
513  }
514 
515  if (!start().node()->document()) {
516  return;
517  }
518 
519  KHTMLView *v = caretPos().node()->document()->view();
520  if (!v) {
521  return;
522  }
523 
524  // qCDebug(KHTML_LOG) << "[NeedsCaretLayout]" << m_needsCaretLayout;
525  if (m_needsCaretLayout) {
526  // repaint old position and calculate new position
527  v->updateContents(getRepaintRect());
528  layoutCaret();
529 
530  // EDIT FIXME: This is an unfortunate hack.
531  // Basically, we can't trust this layout position since we
532  // can't guarantee that the check to see if we are in unrendered
533  // content will work at this point. We may have to wait for
534  // a layout and re-render of the document to happen. So, resetting this
535  // flag will cause another caret layout to happen the first time
536  // that we try to paint the caret after this call. That one will work since
537  // it happens after the document has accounted for any editing
538  // changes which may have been done.
539  // And, we need to leave this layout here so the caret moves right
540  // away after clicking.
541  m_needsCaretLayout = true;
542  }
543  v->updateContents(getRepaintRect());
544 
545 }
546 
547 void Selection::paintCaret(QPainter *p, const QRect &rect)
548 {
549  if (isEmpty()) {
550  return;
551  }
552 
553  if (m_state == NONE) {
554  return;
555  }
556 
557  if (m_needsCaretLayout) {
558  Position pos = caretPos();
559  if (!pos.inRenderedContent()) {
560  // ### wrong wrong wrong wrong wrong. this will break quanta vpl
561  moveToRenderedContent();
562  }
563  layoutCaret();
564  }
565 
566  QRect caretRect(m_caretX, m_caretY, 1, m_caretSize);
567  if (caretRect.intersects(rect)) {
569  QColor c = Qt::black;
572  c = Qt::white;
573  } else {
575  }
576  p->fillRect(caretRect.left(), caretRect.top(), 1, caretRect.height(), c);
577  p->setCompositionMode(oldop);
578  }
579 }
580 
581 void Selection::validate(ETextGranularity granularity)
582 {
583 #ifdef DEBUG_CARET
584  qCDebug(KHTML_LOG) << *this << granularity;
585 #endif
586  // move the base and extent nodes to their equivalent leaf positions
587  bool baseAndExtentEqual = base() == extent();
588  if (base().notEmpty()) {
589 #ifdef DEBUG_CARET
590  qCDebug(KHTML_LOG) << "[base not empty]";
591 #endif
592  Position pos = base().equivalentLeafPosition();
593  assignBase(pos);
594  if (baseAndExtentEqual) {
595  assignExtent(pos);
596  }
597  }
598  if (extent().notEmpty() && !baseAndExtentEqual) {
599  assignExtent(extent().equivalentLeafPosition());
600  }
601 
602  // make sure we do not have a dangling start or end. In particular, if one
603  // of base or extent is empty, we use the other one (which may be empty as
604  // well) for everything, before getting into the code that computes
605  // start + end from base + extent based on granularity.
606  if (base().isEmpty()) {
607  assignBaseAndExtent(extent(), extent());
608  m_baseIsStart = true;
609  } else if (extent().isEmpty()) {
610  assignBaseAndExtent(base(), base());
611  m_baseIsStart = true;
612  } else {
613  // adjust m_baseIsStart as needed
614  if (base().node() == extent().node()) {
615  if (base().offset() > extent().offset()) {
616  m_baseIsStart = false;
617  } else {
618  m_baseIsStart = true;
619  }
620  } else if (nodeIsBeforeNode(base().node(), extent().node())) {
621  m_baseIsStart = true;
622  } else {
623  m_baseIsStart = false;
624  }
625  }
626 
627  // calculate the correct start and end positions
628  if (granularity == CHARACTER) {
629 #ifdef DEBUG_CARET
630  qCDebug(KHTML_LOG) << "[character:baseIsStart]" << m_baseIsStart << base() << extent();
631 #endif
632  if (m_baseIsStart) {
633  assignStartAndEnd(base(), extent());
634  } else {
635  assignStartAndEnd(extent(), base());
636  }
637  } else if (granularity == WORD) {
638  int baseStartOffset = base().offset();
639  int baseEndOffset = base().offset();
640  int extentStartOffset = extent().offset();
641  int extentEndOffset = extent().offset();
642 #ifdef DEBUG_CARET
643  qCDebug(KHTML_LOG) << "WORD GRANULARITY:" << baseStartOffset << baseEndOffset << extentStartOffset << extentEndOffset;
644 #endif
645  if (base().notEmpty() && (base().node()->nodeType() == Node::TEXT_NODE || base().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
646  DOMString t = base().node()->nodeValue();
647  QChar *chars = t.unicode();
648  uint len = t.length();
649 #ifdef DEBUG_CARET
650  qCDebug(KHTML_LOG) << "text:" << QString::fromRawData(chars, len);
651 #endif
652  findWordBoundary(chars, len, base().offset(), &baseStartOffset, &baseEndOffset);
653 #ifdef DEBUG_CARET
654  qCDebug(KHTML_LOG) << "after find word boundary" << baseStartOffset << baseEndOffset;
655 #endif
656  }
657  if (extent().notEmpty() && (extent().node()->nodeType() == Node::TEXT_NODE || extent().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
658  DOMString t = extent().node()->nodeValue();
659  QChar *chars = t.unicode();
660  uint len = t.length();
661 #ifdef DEBUG_CARET
662  qCDebug(KHTML_LOG) << "text:" << QString::fromRawData(chars, len);
663 #endif
664  findWordBoundary(chars, len, extent().offset(), &extentStartOffset, &extentEndOffset);
665 #ifdef DEBUG_CARET
666  qCDebug(KHTML_LOG) << "after find word boundary" << baseStartOffset << baseEndOffset;
667 #endif
668  }
669 #ifdef DEBUG_CARET
670  qCDebug(KHTML_LOG) << "is start:" << m_baseIsStart;
671 #endif
672  if (m_baseIsStart) {
673  assignStart(Position(base().node(), baseStartOffset));
674  assignEnd(Position(extent().node(), extentEndOffset));
675  } else {
676  assignStart(Position(extent().node(), extentStartOffset));
677  assignEnd(Position(base().node(), baseEndOffset));
678  }
679  } else { // granularity == LINE
680  Selection baseSelection = *this;
681  Selection extentSelection = *this;
682  if (base().notEmpty() && (base().node()->nodeType() == Node::TEXT_NODE || base().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
683  if (startAndEndLineNodesIncludingNode(base().node(), base().offset(), baseSelection)) {
684  assignStart(Position(baseSelection.base().node(), baseSelection.base().offset()));
685  assignEnd(Position(baseSelection.extent().node(), baseSelection.extent().offset()));
686  }
687  }
688  if (extent().notEmpty() && (extent().node()->nodeType() == Node::TEXT_NODE || extent().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
689  if (startAndEndLineNodesIncludingNode(extent().node(), extent().offset(), extentSelection)) {
690  assignStart(Position(extentSelection.base().node(), extentSelection.base().offset()));
691  assignEnd(Position(extentSelection.extent().node(), extentSelection.extent().offset()));
692  }
693  }
694  if (m_baseIsStart) {
695  assignStart(baseSelection.start());
696  assignEnd(extentSelection.end());
697  } else {
698  assignStart(extentSelection.start());
699  assignEnd(baseSelection.end());
700  }
701  }
702 
703  // adjust the state
704  if (start().isEmpty() && end().isEmpty()) {
705  m_state = NONE;
706  } else if (start() == end()) {
707  m_state = CARET;
708  } else {
709  m_state = RANGE;
710  }
711 
712  if (start().isEmpty()) {
713  assert(m_state == NONE);
714  }
715 
716  m_needsCaretLayout = true;
717 
718 #if EDIT_DEBUG
719  debugPosition();
720 #endif
721 }
722 
723 bool Selection::moveToRenderedContent()
724 {
725  if (isEmpty()) {
726  return false;
727  }
728 
729  if (m_state != CARET) {
730  return false;
731  }
732 
733  Position pos = start();
734  if (pos.inRenderedContent()) {
735  return true;
736  }
737 
738  // not currently rendered, try moving to prev
739  Position prev = pos.previousCharacterPosition();
740  if (prev != pos && prev.node()->inSameContainingBlockFlowElement(pos.node())) {
741  moveTo(prev);
742  return true;
743  }
744 
745  // could not be moved to prev, try next
746  Position next = pos.nextCharacterPosition();
747  if (next != pos && next.node()->inSameContainingBlockFlowElement(pos.node())) {
748  moveTo(next);
749  return true;
750  }
751 
752  return false;
753 }
754 
755 bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2) const
756 {
757  if (!n1 || !n2) {
758  return true;
759  }
760 
761  if (n1 == n2) {
762  return true;
763  }
764 
765  bool result = false;
766  int n1Depth = 0;
767  int n2Depth = 0;
768 
769  // First we find the depths of the two nodes in the tree (n1Depth, n2Depth)
770  NodeImpl *n = n1;
771  while (n->parentNode()) {
772  n = n->parentNode();
773  n1Depth++;
774  }
775  n = n2;
776  while (n->parentNode()) {
777  n = n->parentNode();
778  n2Depth++;
779  }
780  // Climb up the tree with the deeper node, until both nodes have equal depth
781  while (n2Depth > n1Depth) {
782  n2 = n2->parentNode();
783  n2Depth--;
784  }
785  while (n1Depth > n2Depth) {
786  n1 = n1->parentNode();
787  n1Depth--;
788  }
789  // Climb the tree with both n1 and n2 until they have the same parent
790  while (n1->parentNode() != n2->parentNode()) {
791  n1 = n1->parentNode();
792  n2 = n2->parentNode();
793  }
794  // Iterate through the parent's children until n1 or n2 is found
795  n = n1->parentNode() ? n1->parentNode()->firstChild() : n1->firstChild();
796  while (n) {
797  if (n == n1) {
798  result = true;
799  break;
800  } else if (n == n2) {
801  result = false;
802  break;
803  }
804  n = n->nextSibling();
805  }
806  return result;
807 }
808 
809 static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset)
810 {
811  for (RenderObject *n = renderNode; n; n = n->nextSibling()) {
812  if (n->isText()) {
813  RenderText *textRenderer = static_cast<khtml::RenderText *>(n);
814  for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
815  if (box->m_y == y) {
816  startNode = textRenderer->element();
817  startOffset = box->m_start;
818  return true;
819  }
820  }
821  }
822 
823  if (firstRunAt(n->firstChild(), y, startNode, startOffset)) {
824  return true;
825  }
826  }
827 
828  return false;
829 }
830 
831 static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset)
832 {
833  RenderObject *n = renderNode;
834  if (!n) {
835  return false;
836  }
837  RenderObject *next;
838  while ((next = n->nextSibling())) {
839  n = next;
840  }
841 
842  while (1) {
843  if (lastRunAt(n->firstChild(), y, endNode, endOffset)) {
844  return true;
845  }
846 
847  if (n->isText()) {
848  RenderText *textRenderer = static_cast<khtml::RenderText *>(n);
849  for (InlineTextBox *box = textRenderer->lastTextBox(); box; box = box->prevTextBox()) {
850  if (box->m_y == y) {
851  endNode = textRenderer->element();
852  endOffset = box->m_start + box->m_len;
853  return true;
854  }
855  }
856  }
857 
858  if (n == renderNode) {
859  return false;
860  }
861 
862  n = n->previousSibling();
863  }
864 }
865 
866 static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection)
867 {
868  if (node && node->renderer() && (node->nodeType() == Node::TEXT_NODE || node->nodeType() == Node::CDATA_SECTION_NODE)) {
869  int pos;
870  int selectionPointY;
871  RenderPosition rp = RenderPosition::fromDOMPosition(Position(node, offset));
872  pos = rp.renderedOffset();
873  // RenderText *renderer = static_cast<RenderText *>(node->renderer());
874  // const InlineTextBox * run = renderer->findInlineTextBox( offset, pos );
875  const InlineTextBox *run = static_cast<InlineTextBox *>(node->renderer()->inlineBox(pos));
876  DOMString t = node->nodeValue();
877 
878  if (!run) {
879  return false;
880  }
881 
882  selectionPointY = run->m_y;
883 
884  // Go up to first non-inline element.
885  khtml::RenderObject *renderNode = node->renderer();
886  while (renderNode && renderNode->isInline()) {
887  renderNode = renderNode->parent();
888  }
889 
890  if (renderNode) {
891  renderNode = renderNode->firstChild();
892  }
893 
894  NodeImpl *startNode = nullptr;
895  NodeImpl *endNode = nullptr;
896  long startOffset;
897  long endOffset;
898 
899  // Look for all the first child in the block that is on the same line
900  // as the selection point.
901  if (!firstRunAt(renderNode, selectionPointY, startNode, startOffset)) {
902  return false;
903  }
904 
905  // Look for all the last child in the block that is on the same line
906  // as the selection point.
907  if (!lastRunAt(renderNode, selectionPointY, endNode, endOffset)) {
908  return false;
909  }
910 
911  selection.moveTo(RenderPosition(startNode, startOffset).position(), RenderPosition(endNode, endOffset).position());
912 
913  return true;
914  }
915  return false;
916 }
917 
918 void Selection::debugRenderer(RenderObject *r, bool selected) const
919 {
920  if (r->node()->isElementNode()) {
921  ElementImpl *element = static_cast<ElementImpl *>(r->node());
922  fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element->tagName().string().toLatin1().data());
923  } else if (r->isText()) {
924  RenderText *textRenderer = static_cast<RenderText *>(r);
925  if (textRenderer->stringLength() == 0 || !textRenderer->firstTextBox()) {
926  fprintf(stderr, "%s#text (empty)\n", selected ? "==> " : " ");
927  return;
928  }
929 
930  static const int max = 36;
931  QString text = DOMString(textRenderer->string()).string();
932  int textLength = text.length();
933  if (selected) {
934  int offset = 0;
935  if (r->node() == start().node()) {
936  offset = start().offset();
937  } else if (r->node() == end().node()) {
938  offset = end().offset();
939  }
940 
941  int pos;
942  const InlineTextBox *box = textRenderer->findInlineTextBox(offset, pos);
943  text = text.mid(box->m_start, box->m_len);
944 
945  QString show;
946  int mid = max / 2;
947  int caret = 0;
948 
949  // text is shorter than max
950  if (textLength < max) {
951  show = text;
952  caret = pos;
953  }
954 
955  // too few characters to left
956  else if (pos - mid < 0) {
957  show = text.left(max - 3) + "...";
958  caret = pos;
959  }
960 
961  // enough characters on each side
962  else if (pos - mid >= 0 && pos + mid <= textLength) {
963  show = "..." + text.mid(pos - mid + 3, max - 6) + "...";
964  caret = mid;
965  }
966 
967  // too few characters on right
968  else {
969  show = "..." + text.right(max - 3);
970  caret = pos - (textLength - show.length());
971  }
972 
973  show = show.replace("\n", " ");
974  show = show.replace("\r", " ");
975  fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.toLatin1().data(), pos);
976  fprintf(stderr, " ");
977  for (int i = 0; i < caret; i++) {
978  fprintf(stderr, " ");
979  }
980  fprintf(stderr, "^\n");
981  } else {
982  if ((int)text.length() > max) {
983  text = text.left(max - 3) + "...";
984  } else {
985  text = text.left(max);
986  }
987  fprintf(stderr, " #text : \"%s\"\n", text.toLatin1().data());
988  }
989  }
990 }
991 
992 void Selection::debugPosition() const
993 {
994  if (!start().node()) {
995  return;
996  }
997 
998  //static int context = 5;
999 
1000  //RenderObject *r = 0;
1001 
1002  fprintf(stderr, "Selection =================\n");
1003 
1004  if (start() == end()) {
1005  Position pos = start();
1006  Position upstream = pos.equivalentUpstreamPosition();
1007  Position downstream = pos.equivalentDownstreamPosition();
1008  /*FIXME:use qCDebug(KHTML_LOG) fprintf(stderr, "upstream: %s %p:%ld\n", getTagName(upstream.node()->id())
1009  , upstream.node(), upstream.offset());
1010  fprintf(stderr, "pos: %s %p:%ld\n", getTagName(pos.node()->id())
1011  , pos.node(), pos.offset());
1012  fprintf(stderr, "downstream: %s %p:%ld\n", getTagName(downstream.node()->id())
1013  , downstream.node(), downstream.offset());*/
1014  } else {
1015  Position pos = start();
1016  Position upstream = pos.equivalentUpstreamPosition();
1017  Position downstream = pos.equivalentDownstreamPosition();
1018  /*FIXME: use qCDebug(KHTML_LOG) fprintf(stderr, "upstream: %s %p:%ld\n", getTagName(upstream.node()->id())
1019  , upstream.node(), upstream.offset());
1020  fprintf(stderr, "start: %s %p:%ld\n", getTagName(pos.node()->id())
1021  , pos.node(), pos.offset());
1022  fprintf(stderr, "downstream: %s %p:%ld\n", getTagName(downstream.node()->id())
1023  , downstream.node(), downstream.offset());
1024  fprintf(stderr, "-----------------------------------\n");*/
1025  pos = end();
1026  upstream = pos.equivalentUpstreamPosition();
1027  downstream = pos.equivalentDownstreamPosition();
1028  /*FIXME: use qCDebug(KHTML_LOG) fprintf(stderr, "upstream: %s %p:%ld\n", getTagName(upstream.node()->id())
1029  , upstream.node(), upstream.offset());
1030  fprintf(stderr, "end: %s %p:%ld\n", getTagName(pos.node()->id())
1031  , pos.node(), pos.offset());
1032  fprintf(stderr, "downstream: %s %p:%ld\n", getTagName(downstream.node()->id())
1033  , downstream.node(), downstream.offset());
1034  fprintf(stderr, "-----------------------------------\n");*/
1035  }
1036 
1037 #if 0
1038  int back = 0;
1039  r = start().node()->renderer();
1040  for (int i = 0; i < context; i++, back++) {
1041  if (r->previousRenderer()) {
1042  r = r->previousRenderer();
1043  } else {
1044  break;
1045  }
1046  }
1047  for (int i = 0; i < back; i++) {
1048  debugRenderer(r, false);
1049  r = r->nextRenderer();
1050  }
1051 
1052  fprintf(stderr, "\n");
1053 
1054  if (start().node() == end().node()) {
1055  debugRenderer(start().node()->renderer(), true);
1056  } else
1057  for (r = start().node()->renderer(); r && r != end().node()->renderer(); r = r->nextRenderer()) {
1058  debugRenderer(r, true);
1059  }
1060 
1061  fprintf(stderr, "\n");
1062 
1063  r = end().node()->renderer();
1064  for (int i = 0; i < context; i++) {
1065  if (r->nextRenderer()) {
1066  r = r->nextRenderer();
1067  debugRenderer(r, false);
1068  } else {
1069  break;
1070  }
1071  }
1072 #endif
1073 
1074  fprintf(stderr, "================================\n");
1075 }
1076 
1077 QDebug operator<<(QDebug stream, const Selection &selection)
1078 {
1079  stream << "Selection["
1080  << selection.base()
1081  << selection.extent()
1082  << selection.start()
1083  << selection.end()
1084  << selection.affinity() << "]";
1085  return stream;
1086 }
1087 
1088 } // namespace DOM
void updateContents(const QRect &r)
Requests an update of the content area.
Definition: khtmlview.cpp:805
void fillRect(const QRectF &rectangle, const QBrush &brush)
void setCompositionMode(QPainter::CompositionMode mode)
This class is khtml&#39;s main class.
Definition: khtml_part.h:208
MESSAGECORE_EXPORT KMime::Content * next(KMime::Content *node, bool allowChildren=true)
Renders and displays HTML in a QScrollArea.
Definition: khtmlview.h:97
QPaintEngine * paintEngine() const const
QString fromRawData(const QChar *unicode, int size)
const QList< QKeySequence > & back()
QPainter::CompositionMode compositionMode() const const
This class implements the basic string we use in the DOM.
Definition: dom_string.h:44
QString right(int n) const const
QCA_EXPORT void init()
QString & replace(int position, int n, QChar after)
This library provides a full-featured HTML parser and widget.
const QList< QKeySequence > & end()
QDataStream & operator<<(QDataStream &out, const KDateTime::Spec &spec)
QByteArray toLatin1() const const
QString mid(int position, int n) const const
KIOWIDGETS_EXPORT bool run(const QUrl &_url, bool _is_local)
Base Class for all rendering tree objects.
int length() const const
char * data()
QString left(int n) const const
DOM::Document document() const
Returns a reference to the DOM document.
bool hasFeature(QPaintEngine::PaintEngineFeatures feature) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 25 2021 22:48:13 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.