File indexing completed on 2024-05-05 12:16:46
0001 /* 0002 * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. 0003 * 0004 * Redistribution and use in source and binary forms, with or without 0005 * modification, are permitted provided that the following conditions 0006 * are met: 0007 * 1. Redistributions of source code must retain the above copyright 0008 * notice, this list of conditions and the following disclaimer. 0009 * 2. Redistributions in binary form must reproduce the above copyright 0010 * notice, this list of conditions and the following disclaimer in the 0011 * documentation and/or other materials provided with the distribution. 0012 * 0013 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 0014 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 0015 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 0016 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 0017 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 0018 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 0019 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 0020 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 0021 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 0022 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 0023 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 0024 */ 0025 0026 #include "dom_selection.h" 0027 0028 #include "khtml_part.h" 0029 #include "khtmlpart_p.h" 0030 #include "khtmlview.h" 0031 #include "dom/dom2_range.h" 0032 #include "dom/dom_node.h" 0033 #include "dom/dom_string.h" 0034 #include "rendering/render_object.h" 0035 #include "rendering/render_style.h" 0036 #include "rendering/render_text.h" 0037 #include "xml/dom_docimpl.h" 0038 #include "xml/dom_positioniterator.h" 0039 #include "xml/dom_elementimpl.h" 0040 #include "xml/dom_nodeimpl.h" 0041 #include "xml/dom_textimpl.h" 0042 0043 #include <QEvent> 0044 #include <QPainter> 0045 #include <QPaintEngine> 0046 #include <QRect> 0047 0048 #define EDIT_DEBUG 0 0049 #define DEBUG_CARET 0050 0051 using khtml::EditorContext; 0052 using khtml::findWordBoundary; 0053 using khtml::InlineTextBox; 0054 using khtml::RenderObject; 0055 using khtml::RenderText; 0056 using khtml::RenderPosition; 0057 0058 namespace DOM 0059 { 0060 0061 static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset); 0062 static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset); 0063 static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection); 0064 0065 static inline Position &emptyPosition() 0066 { 0067 static Position EmptyPosition = Position(); 0068 return EmptyPosition; 0069 } 0070 0071 Selection::Selection() 0072 { 0073 init(); 0074 } 0075 0076 Selection::Selection(const Position &pos) 0077 { 0078 init(); 0079 assignBaseAndExtent(pos, pos); 0080 validate(); 0081 } 0082 0083 Selection::Selection(const Range &r) 0084 { 0085 const Position start(r.startContainer().handle(), r.startOffset()); 0086 const Position end(r.endContainer().handle(), r.endOffset()); 0087 0088 init(); 0089 assignBaseAndExtent(start, end); 0090 validate(); 0091 } 0092 0093 Selection::Selection(const Position &base, const Position &extent) 0094 { 0095 init(); 0096 assignBaseAndExtent(base, extent); 0097 validate(); 0098 } 0099 0100 Selection::Selection(const Selection &o) 0101 { 0102 init(); 0103 0104 assignBaseAndExtent(o.base(), o.extent()); 0105 assignStartAndEnd(o.start(), o.end()); 0106 0107 m_state = o.m_state; 0108 m_affinity = o.m_affinity; 0109 0110 m_baseIsStart = o.m_baseIsStart; 0111 m_needsCaretLayout = o.m_needsCaretLayout; 0112 m_modifyBiasSet = o.m_modifyBiasSet; 0113 0114 // Only copy the coordinates over if the other object 0115 // has had a layout, otherwise keep the current 0116 // coordinates. This prevents drawing artifacts from 0117 // remaining when the caret is painted and then moves, 0118 // and the old rectangle needs to be repainted. 0119 if (!m_needsCaretLayout) { 0120 m_caretX = o.m_caretX; 0121 m_caretY = o.m_caretY; 0122 m_caretSize = o.m_caretSize; 0123 } 0124 } 0125 0126 void Selection::init() 0127 { 0128 m_base = m_extent = m_start = m_end = emptyPosition(); 0129 m_state = NONE; 0130 m_caretX = 0; 0131 m_caretY = 0; 0132 m_caretSize = 0; 0133 m_baseIsStart = true; 0134 m_needsCaretLayout = true; 0135 m_modifyBiasSet = false; 0136 m_affinity = DOWNSTREAM; 0137 } 0138 0139 Selection &Selection::operator=(const Selection &o) 0140 { 0141 assignBaseAndExtent(o.base(), o.extent()); 0142 assignStartAndEnd(o.start(), o.end()); 0143 0144 m_state = o.m_state; 0145 m_affinity = o.m_affinity; 0146 0147 m_baseIsStart = o.m_baseIsStart; 0148 m_needsCaretLayout = o.m_needsCaretLayout; 0149 m_modifyBiasSet = o.m_modifyBiasSet; 0150 0151 // Only copy the coordinates over if the other object 0152 // has had a layout, otherwise keep the current 0153 // coordinates. This prevents drawing artifacts from 0154 // remaining when the caret is painted and then moves, 0155 // and the old rectangle needs to be repainted. 0156 if (!m_needsCaretLayout) { 0157 m_caretX = o.m_caretX; 0158 m_caretY = o.m_caretY; 0159 m_caretSize = o.m_caretSize; 0160 } 0161 0162 return *this; 0163 } 0164 0165 void Selection::setAffinity(EAffinity affinity) 0166 { 0167 if (affinity == m_affinity) { 0168 return; 0169 } 0170 0171 m_affinity = affinity; 0172 setNeedsLayout(); 0173 } 0174 0175 void Selection::moveTo(const Range &r) 0176 { 0177 Position start(r.startContainer().handle(), r.startOffset()); 0178 Position end(r.endContainer().handle(), r.endOffset()); 0179 moveTo(start, end); 0180 } 0181 0182 void Selection::moveTo(const Selection &o) 0183 { 0184 moveTo(o.start(), o.end()); 0185 } 0186 0187 void Selection::moveTo(const Position &pos) 0188 { 0189 moveTo(pos, pos); 0190 } 0191 0192 void Selection::moveTo(const Position &base, const Position &extent) 0193 { 0194 // kdDebug(6200) << "Selection::moveTo: base(" << base.node() << "," << base.offset() << "), extent(" << extent.node() << "," << extent.offset() << ")"; 0195 #ifdef DEBUG_CARET 0196 qCDebug(KHTML_LOG) << *this << base << extent; 0197 #endif 0198 assignBaseAndExtent(base, extent); 0199 validate(); 0200 } 0201 0202 bool Selection::modify(EAlter alter, EDirection dir, ETextGranularity granularity) 0203 { 0204 Position pos; 0205 0206 switch (dir) { 0207 // EDIT FIXME: This needs to handle bidi 0208 case RIGHT: 0209 case FORWARD: 0210 if (alter == EXTEND) { 0211 if (!m_modifyBiasSet) { 0212 m_modifyBiasSet = true; 0213 assignBaseAndExtent(start(), end()); 0214 } 0215 switch (granularity) { 0216 case CHARACTER: 0217 pos = extent().nextCharacterPosition(); 0218 break; 0219 case WORD: 0220 pos = extent().nextWordPosition(); 0221 break; 0222 case LINE: 0223 pos = extent().nextLinePosition(xPosForVerticalArrowNavigation(EXTENT)); 0224 break; 0225 case PARAGRAPH: 0226 // not implemented 0227 break; 0228 } 0229 } else { 0230 m_modifyBiasSet = false; 0231 switch (granularity) { 0232 case CHARACTER: 0233 pos = (state() == RANGE) ? end() : extent().nextCharacterPosition(); 0234 break; 0235 case WORD: 0236 pos = extent().nextWordPosition(); 0237 break; 0238 case LINE: 0239 pos = end().nextLinePosition(xPosForVerticalArrowNavigation(END, state() == RANGE)); 0240 break; 0241 case PARAGRAPH: 0242 // not implemented 0243 break; 0244 } 0245 } 0246 break; 0247 // EDIT FIXME: This needs to handle bidi 0248 case LEFT: 0249 case BACKWARD: 0250 if (alter == EXTEND) { 0251 if (!m_modifyBiasSet) { 0252 m_modifyBiasSet = true; 0253 assignBaseAndExtent(end(), start()); 0254 } 0255 switch (granularity) { 0256 case CHARACTER: 0257 pos = extent().previousCharacterPosition(); 0258 break; 0259 case WORD: 0260 pos = extent().previousWordPosition(); 0261 break; 0262 case LINE: 0263 pos = extent().previousLinePosition(xPosForVerticalArrowNavigation(EXTENT)); 0264 break; 0265 case PARAGRAPH: 0266 // not implemented 0267 break; 0268 } 0269 } else { 0270 m_modifyBiasSet = false; 0271 switch (granularity) { 0272 case CHARACTER: 0273 pos = (state() == RANGE) ? start() : extent().previousCharacterPosition(); 0274 break; 0275 case WORD: 0276 pos = extent().previousWordPosition(); 0277 break; 0278 case LINE: 0279 pos = start().previousLinePosition(xPosForVerticalArrowNavigation(START, state() == RANGE)); 0280 break; 0281 case PARAGRAPH: 0282 // not implemented 0283 break; 0284 } 0285 } 0286 break; 0287 } 0288 0289 if (pos.isEmpty()) { 0290 return false; 0291 } 0292 0293 if (alter == MOVE) { 0294 moveTo(pos); 0295 } else { // alter == EXTEND 0296 setExtent(pos); 0297 } 0298 0299 return true; 0300 } 0301 0302 bool Selection::expandUsingGranularity(ETextGranularity granularity) 0303 { 0304 if (state() == NONE) { 0305 return false; 0306 } 0307 0308 validate(granularity); 0309 return true; 0310 } 0311 0312 int Selection::xPosForVerticalArrowNavigation(EPositionType type, bool recalc) const 0313 { 0314 int x = 0; 0315 0316 if (state() == NONE) { 0317 return x; 0318 } 0319 0320 Position pos; 0321 switch (type) { 0322 case START: 0323 pos = start(); 0324 break; 0325 case END: 0326 pos = end(); 0327 break; 0328 case BASE: 0329 pos = base(); 0330 break; 0331 case EXTENT: 0332 pos = extent(); 0333 break; 0334 case CARETPOS: 0335 pos = caretPos(); 0336 break; 0337 } 0338 0339 KHTMLPart *part = pos.node()->document()->part(); 0340 if (!part) { 0341 return x; 0342 } 0343 0344 if (recalc || part->d->editor_context.m_xPosForVerticalArrowNavigation == EditorContext::NoXPosForVerticalArrowNavigation 0345 ) { 0346 int y, w, h; 0347 if (pos.node()->renderer()) { 0348 pos.node()->renderer()->caretPos(pos.renderedOffset(), 0, x, y, w, h); 0349 } 0350 part->d->editor_context.m_xPosForVerticalArrowNavigation = x; 0351 } else { 0352 x = part->d->editor_context.m_xPosForVerticalArrowNavigation; 0353 } 0354 0355 return x; 0356 } 0357 0358 void Selection::clear() 0359 { 0360 assignBaseAndExtent(emptyPosition(), emptyPosition()); 0361 validate(); 0362 } 0363 0364 void Selection::collapse() 0365 { 0366 moveTo(caretPos()); 0367 } 0368 0369 void Selection::setBase(const Position &pos) 0370 { 0371 assignBase(pos); 0372 validate(); 0373 } 0374 0375 void Selection::setExtent(const Position &pos) 0376 { 0377 assignExtent(pos); 0378 validate(); 0379 } 0380 0381 void Selection::setBaseAndExtent(const Position &base, const Position &extent) 0382 { 0383 assignBaseAndExtent(base, extent); 0384 validate(); 0385 } 0386 0387 void Selection::setStart(const Position &pos) 0388 { 0389 assignStart(pos); 0390 validate(); 0391 } 0392 0393 void Selection::setEnd(const Position &pos) 0394 { 0395 assignEnd(pos); 0396 validate(); 0397 } 0398 0399 void Selection::setStartAndEnd(const Position &start, const Position &end) 0400 { 0401 assignStartAndEnd(start, end); 0402 validate(); 0403 } 0404 0405 void Selection::setNeedsLayout(bool flag) 0406 { 0407 m_needsCaretLayout = flag; 0408 } 0409 0410 void Selection::getRange(NodeImpl *&st, long &so, NodeImpl *&en, long &eo) const 0411 { 0412 if (isEmpty()) { 0413 st = en = nullptr; so = eo = 0; 0414 return; 0415 } 0416 0417 // Make sure we have an updated layout since this function is called 0418 // in the course of running edit commands which modify the DOM. 0419 // Failing to call this can result in equivalentXXXPosition calls returning 0420 // incorrect results. 0421 start().node()->document()->updateLayout(); 0422 0423 Position s, e; 0424 if (state() == CARET) { 0425 // If the selection is a caret, move the range start upstream. This helps us match 0426 // the conventions of text editors tested, which make style determinations based 0427 // on the character before the caret, if any. 0428 s = start().equivalentUpstreamPosition().equivalentRangeCompliantPosition(); 0429 e = s; 0430 } else { 0431 // If the selection is a range, select the minimum range that encompasses the selection. 0432 // Again, this is to match the conventions of text editors tested, which make style 0433 // determinations based on the first character of the selection. 0434 // For instance, this operation helps to make sure that the "X" selected below is the 0435 // only thing selected. The range should not be allowed to "leak" out to the end of the 0436 // previous text node, or to the beginning of the next text node, each of which has a 0437 // different style. 0438 // 0439 // On a treasure map, <b>X</b> marks the spot. 0440 // ^ selected 0441 // 0442 assert(state() == RANGE); 0443 s = start().equivalentDownstreamPosition(); 0444 e = end().equivalentUpstreamPosition(); 0445 if ((s.node() == e.node() && s.offset() > e.offset()) || !nodeIsBeforeNode(s.node(), e.node())) { 0446 // Make sure the start is before the end. 0447 // The end can wind up before the start if collapsed whitespace is the only thing selected. 0448 Position tmp = s; 0449 s = e; 0450 e = tmp; 0451 } 0452 s = s.equivalentRangeCompliantPosition(); 0453 e = e.equivalentRangeCompliantPosition(); 0454 } 0455 0456 st = s.node(); 0457 so = s.offset(); 0458 en = e.node(); 0459 eo = e.offset(); 0460 } 0461 0462 Range Selection::toRange() const 0463 { 0464 if (isEmpty()) { 0465 return Range(); 0466 } 0467 0468 NodeImpl *start, *end; 0469 long so, eo; 0470 getRange(start, so, end, eo); 0471 return Range(Node(start), so, Node(end), eo); 0472 } 0473 0474 void Selection::layoutCaret() 0475 { 0476 if (isEmpty() || !caretPos().node()->renderer()) { 0477 m_caretX = m_caretY = m_caretSize = 0; 0478 } else { 0479 // EDIT FIXME: Enhance call to pass along selection 0480 // upstream/downstream affinity to get the right position. 0481 int w; 0482 int offset = RenderPosition::fromDOMPosition(caretPos()).renderedOffset(); 0483 #ifdef DEBUG_CARET 0484 qCDebug(KHTML_LOG) << "[before caretPos()]" << m_caretX; 0485 #endif 0486 caretPos().node()->renderer()->caretPos(offset, true, m_caretX, m_caretY, w, m_caretSize); 0487 #ifdef DEBUG_CARET 0488 qCDebug(KHTML_LOG) << "[after caretPos()]" << m_caretX; 0489 #endif 0490 } 0491 0492 m_needsCaretLayout = false; 0493 } 0494 0495 QRect Selection::getRepaintRect() const 0496 { 0497 if (m_needsCaretLayout) { 0498 const_cast<Selection *>(this)->layoutCaret(); 0499 } 0500 0501 // EDIT FIXME: fudge a bit to make sure we don't leave behind artifacts 0502 return QRect(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2); 0503 } 0504 0505 void Selection::needsCaretRepaint() 0506 { 0507 if (isEmpty()) { 0508 return; 0509 } 0510 0511 if (!start().node()) { 0512 return; 0513 } 0514 0515 if (!start().node()->document()) { 0516 return; 0517 } 0518 0519 KHTMLView *v = caretPos().node()->document()->view(); 0520 if (!v) { 0521 return; 0522 } 0523 0524 // qCDebug(KHTML_LOG) << "[NeedsCaretLayout]" << m_needsCaretLayout; 0525 if (m_needsCaretLayout) { 0526 // repaint old position and calculate new position 0527 v->updateContents(getRepaintRect()); 0528 layoutCaret(); 0529 0530 // EDIT FIXME: This is an unfortunate hack. 0531 // Basically, we can't trust this layout position since we 0532 // can't guarantee that the check to see if we are in unrendered 0533 // content will work at this point. We may have to wait for 0534 // a layout and re-render of the document to happen. So, resetting this 0535 // flag will cause another caret layout to happen the first time 0536 // that we try to paint the caret after this call. That one will work since 0537 // it happens after the document has accounted for any editing 0538 // changes which may have been done. 0539 // And, we need to leave this layout here so the caret moves right 0540 // away after clicking. 0541 m_needsCaretLayout = true; 0542 } 0543 v->updateContents(getRepaintRect()); 0544 0545 } 0546 0547 void Selection::paintCaret(QPainter *p, const QRect &rect) 0548 { 0549 if (isEmpty()) { 0550 return; 0551 } 0552 0553 if (m_state == NONE) { 0554 return; 0555 } 0556 0557 if (m_needsCaretLayout) { 0558 Position pos = caretPos(); 0559 if (!pos.inRenderedContent()) { 0560 // ### wrong wrong wrong wrong wrong. this will break quanta vpl 0561 moveToRenderedContent(); 0562 } 0563 layoutCaret(); 0564 } 0565 0566 QRect caretRect(m_caretX, m_caretY, 1, m_caretSize); 0567 if (caretRect.intersects(rect)) { 0568 QPainter::CompositionMode oldop = p->compositionMode(); 0569 QColor c = Qt::black; 0570 if (p->paintEngine() && p->paintEngine()->hasFeature(QPaintEngine::BlendModes)) { 0571 p->setCompositionMode(QPainter::CompositionMode_Difference); 0572 c = Qt::white; 0573 } else { 0574 p->setCompositionMode(QPainter::CompositionMode_Xor); 0575 } 0576 p->fillRect(caretRect.left(), caretRect.top(), 1, caretRect.height(), c); 0577 p->setCompositionMode(oldop); 0578 } 0579 } 0580 0581 void Selection::validate(ETextGranularity granularity) 0582 { 0583 #ifdef DEBUG_CARET 0584 qCDebug(KHTML_LOG) << *this << granularity; 0585 #endif 0586 // move the base and extent nodes to their equivalent leaf positions 0587 bool baseAndExtentEqual = base() == extent(); 0588 if (base().notEmpty()) { 0589 #ifdef DEBUG_CARET 0590 qCDebug(KHTML_LOG) << "[base not empty]"; 0591 #endif 0592 Position pos = base().equivalentLeafPosition(); 0593 assignBase(pos); 0594 if (baseAndExtentEqual) { 0595 assignExtent(pos); 0596 } 0597 } 0598 if (extent().notEmpty() && !baseAndExtentEqual) { 0599 assignExtent(extent().equivalentLeafPosition()); 0600 } 0601 0602 // make sure we do not have a dangling start or end. In particular, if one 0603 // of base or extent is empty, we use the other one (which may be empty as 0604 // well) for everything, before getting into the code that computes 0605 // start + end from base + extent based on granularity. 0606 if (base().isEmpty()) { 0607 assignBaseAndExtent(extent(), extent()); 0608 m_baseIsStart = true; 0609 } else if (extent().isEmpty()) { 0610 assignBaseAndExtent(base(), base()); 0611 m_baseIsStart = true; 0612 } else { 0613 // adjust m_baseIsStart as needed 0614 if (base().node() == extent().node()) { 0615 if (base().offset() > extent().offset()) { 0616 m_baseIsStart = false; 0617 } else { 0618 m_baseIsStart = true; 0619 } 0620 } else if (nodeIsBeforeNode(base().node(), extent().node())) { 0621 m_baseIsStart = true; 0622 } else { 0623 m_baseIsStart = false; 0624 } 0625 } 0626 0627 // calculate the correct start and end positions 0628 if (granularity == CHARACTER) { 0629 #ifdef DEBUG_CARET 0630 qCDebug(KHTML_LOG) << "[character:baseIsStart]" << m_baseIsStart << base() << extent(); 0631 #endif 0632 if (m_baseIsStart) { 0633 assignStartAndEnd(base(), extent()); 0634 } else { 0635 assignStartAndEnd(extent(), base()); 0636 } 0637 } else if (granularity == WORD) { 0638 int baseStartOffset = base().offset(); 0639 int baseEndOffset = base().offset(); 0640 int extentStartOffset = extent().offset(); 0641 int extentEndOffset = extent().offset(); 0642 #ifdef DEBUG_CARET 0643 qCDebug(KHTML_LOG) << "WORD GRANULARITY:" << baseStartOffset << baseEndOffset << extentStartOffset << extentEndOffset; 0644 #endif 0645 if (base().notEmpty() && (base().node()->nodeType() == Node::TEXT_NODE || base().node()->nodeType() == Node::CDATA_SECTION_NODE)) { 0646 DOMString t = base().node()->nodeValue(); 0647 QChar *chars = t.unicode(); 0648 uint len = t.length(); 0649 #ifdef DEBUG_CARET 0650 qCDebug(KHTML_LOG) << "text:" << QString::fromRawData(chars, len); 0651 #endif 0652 findWordBoundary(chars, len, base().offset(), &baseStartOffset, &baseEndOffset); 0653 #ifdef DEBUG_CARET 0654 qCDebug(KHTML_LOG) << "after find word boundary" << baseStartOffset << baseEndOffset; 0655 #endif 0656 } 0657 if (extent().notEmpty() && (extent().node()->nodeType() == Node::TEXT_NODE || extent().node()->nodeType() == Node::CDATA_SECTION_NODE)) { 0658 DOMString t = extent().node()->nodeValue(); 0659 QChar *chars = t.unicode(); 0660 uint len = t.length(); 0661 #ifdef DEBUG_CARET 0662 qCDebug(KHTML_LOG) << "text:" << QString::fromRawData(chars, len); 0663 #endif 0664 findWordBoundary(chars, len, extent().offset(), &extentStartOffset, &extentEndOffset); 0665 #ifdef DEBUG_CARET 0666 qCDebug(KHTML_LOG) << "after find word boundary" << baseStartOffset << baseEndOffset; 0667 #endif 0668 } 0669 #ifdef DEBUG_CARET 0670 qCDebug(KHTML_LOG) << "is start:" << m_baseIsStart; 0671 #endif 0672 if (m_baseIsStart) { 0673 assignStart(Position(base().node(), baseStartOffset)); 0674 assignEnd(Position(extent().node(), extentEndOffset)); 0675 } else { 0676 assignStart(Position(extent().node(), extentStartOffset)); 0677 assignEnd(Position(base().node(), baseEndOffset)); 0678 } 0679 } else { // granularity == LINE 0680 Selection baseSelection = *this; 0681 Selection extentSelection = *this; 0682 if (base().notEmpty() && (base().node()->nodeType() == Node::TEXT_NODE || base().node()->nodeType() == Node::CDATA_SECTION_NODE)) { 0683 if (startAndEndLineNodesIncludingNode(base().node(), base().offset(), baseSelection)) { 0684 assignStart(Position(baseSelection.base().node(), baseSelection.base().offset())); 0685 assignEnd(Position(baseSelection.extent().node(), baseSelection.extent().offset())); 0686 } 0687 } 0688 if (extent().notEmpty() && (extent().node()->nodeType() == Node::TEXT_NODE || extent().node()->nodeType() == Node::CDATA_SECTION_NODE)) { 0689 if (startAndEndLineNodesIncludingNode(extent().node(), extent().offset(), extentSelection)) { 0690 assignStart(Position(extentSelection.base().node(), extentSelection.base().offset())); 0691 assignEnd(Position(extentSelection.extent().node(), extentSelection.extent().offset())); 0692 } 0693 } 0694 if (m_baseIsStart) { 0695 assignStart(baseSelection.start()); 0696 assignEnd(extentSelection.end()); 0697 } else { 0698 assignStart(extentSelection.start()); 0699 assignEnd(baseSelection.end()); 0700 } 0701 } 0702 0703 // adjust the state 0704 if (start().isEmpty() && end().isEmpty()) { 0705 m_state = NONE; 0706 } else if (start() == end()) { 0707 m_state = CARET; 0708 } else { 0709 m_state = RANGE; 0710 } 0711 0712 if (start().isEmpty()) { 0713 assert(m_state == NONE); 0714 } 0715 0716 m_needsCaretLayout = true; 0717 0718 #if EDIT_DEBUG 0719 debugPosition(); 0720 #endif 0721 } 0722 0723 bool Selection::moveToRenderedContent() 0724 { 0725 if (isEmpty()) { 0726 return false; 0727 } 0728 0729 if (m_state != CARET) { 0730 return false; 0731 } 0732 0733 Position pos = start(); 0734 if (pos.inRenderedContent()) { 0735 return true; 0736 } 0737 0738 // not currently rendered, try moving to prev 0739 Position prev = pos.previousCharacterPosition(); 0740 if (prev != pos && prev.node()->inSameContainingBlockFlowElement(pos.node())) { 0741 moveTo(prev); 0742 return true; 0743 } 0744 0745 // could not be moved to prev, try next 0746 Position next = pos.nextCharacterPosition(); 0747 if (next != pos && next.node()->inSameContainingBlockFlowElement(pos.node())) { 0748 moveTo(next); 0749 return true; 0750 } 0751 0752 return false; 0753 } 0754 0755 bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2) const 0756 { 0757 if (!n1 || !n2) { 0758 return true; 0759 } 0760 0761 if (n1 == n2) { 0762 return true; 0763 } 0764 0765 bool result = false; 0766 int n1Depth = 0; 0767 int n2Depth = 0; 0768 0769 // First we find the depths of the two nodes in the tree (n1Depth, n2Depth) 0770 NodeImpl *n = n1; 0771 while (n->parentNode()) { 0772 n = n->parentNode(); 0773 n1Depth++; 0774 } 0775 n = n2; 0776 while (n->parentNode()) { 0777 n = n->parentNode(); 0778 n2Depth++; 0779 } 0780 // Climb up the tree with the deeper node, until both nodes have equal depth 0781 while (n2Depth > n1Depth) { 0782 n2 = n2->parentNode(); 0783 n2Depth--; 0784 } 0785 while (n1Depth > n2Depth) { 0786 n1 = n1->parentNode(); 0787 n1Depth--; 0788 } 0789 // Climb the tree with both n1 and n2 until they have the same parent 0790 while (n1->parentNode() != n2->parentNode()) { 0791 n1 = n1->parentNode(); 0792 n2 = n2->parentNode(); 0793 } 0794 // Iterate through the parent's children until n1 or n2 is found 0795 n = n1->parentNode() ? n1->parentNode()->firstChild() : n1->firstChild(); 0796 while (n) { 0797 if (n == n1) { 0798 result = true; 0799 break; 0800 } else if (n == n2) { 0801 result = false; 0802 break; 0803 } 0804 n = n->nextSibling(); 0805 } 0806 return result; 0807 } 0808 0809 static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset) 0810 { 0811 for (RenderObject *n = renderNode; n; n = n->nextSibling()) { 0812 if (n->isText()) { 0813 RenderText *textRenderer = static_cast<khtml::RenderText *>(n); 0814 for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { 0815 if (box->m_y == y) { 0816 startNode = textRenderer->element(); 0817 startOffset = box->m_start; 0818 return true; 0819 } 0820 } 0821 } 0822 0823 if (firstRunAt(n->firstChild(), y, startNode, startOffset)) { 0824 return true; 0825 } 0826 } 0827 0828 return false; 0829 } 0830 0831 static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset) 0832 { 0833 RenderObject *n = renderNode; 0834 if (!n) { 0835 return false; 0836 } 0837 RenderObject *next; 0838 while ((next = n->nextSibling())) { 0839 n = next; 0840 } 0841 0842 while (1) { 0843 if (lastRunAt(n->firstChild(), y, endNode, endOffset)) { 0844 return true; 0845 } 0846 0847 if (n->isText()) { 0848 RenderText *textRenderer = static_cast<khtml::RenderText *>(n); 0849 for (InlineTextBox *box = textRenderer->lastTextBox(); box; box = box->prevTextBox()) { 0850 if (box->m_y == y) { 0851 endNode = textRenderer->element(); 0852 endOffset = box->m_start + box->m_len; 0853 return true; 0854 } 0855 } 0856 } 0857 0858 if (n == renderNode) { 0859 return false; 0860 } 0861 0862 n = n->previousSibling(); 0863 } 0864 } 0865 0866 static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection) 0867 { 0868 if (node && node->renderer() && (node->nodeType() == Node::TEXT_NODE || node->nodeType() == Node::CDATA_SECTION_NODE)) { 0869 int pos; 0870 int selectionPointY; 0871 RenderPosition rp = RenderPosition::fromDOMPosition(Position(node, offset)); 0872 pos = rp.renderedOffset(); 0873 // RenderText *renderer = static_cast<RenderText *>(node->renderer()); 0874 // const InlineTextBox * run = renderer->findInlineTextBox( offset, pos ); 0875 const InlineTextBox *run = static_cast<InlineTextBox *>(node->renderer()->inlineBox(pos)); 0876 DOMString t = node->nodeValue(); 0877 0878 if (!run) { 0879 return false; 0880 } 0881 0882 selectionPointY = run->m_y; 0883 0884 // Go up to first non-inline element. 0885 khtml::RenderObject *renderNode = node->renderer(); 0886 while (renderNode && renderNode->isInline()) { 0887 renderNode = renderNode->parent(); 0888 } 0889 0890 if (renderNode) { 0891 renderNode = renderNode->firstChild(); 0892 } 0893 0894 NodeImpl *startNode = nullptr; 0895 NodeImpl *endNode = nullptr; 0896 long startOffset; 0897 long endOffset; 0898 0899 // Look for all the first child in the block that is on the same line 0900 // as the selection point. 0901 if (!firstRunAt(renderNode, selectionPointY, startNode, startOffset)) { 0902 return false; 0903 } 0904 0905 // Look for all the last child in the block that is on the same line 0906 // as the selection point. 0907 if (!lastRunAt(renderNode, selectionPointY, endNode, endOffset)) { 0908 return false; 0909 } 0910 0911 selection.moveTo(RenderPosition(startNode, startOffset).position(), RenderPosition(endNode, endOffset).position()); 0912 0913 return true; 0914 } 0915 return false; 0916 } 0917 0918 void Selection::debugRenderer(RenderObject *r, bool selected) const 0919 { 0920 if (r->node()->isElementNode()) { 0921 ElementImpl *element = static_cast<ElementImpl *>(r->node()); 0922 fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element->tagName().string().toLatin1().data()); 0923 } else if (r->isText()) { 0924 RenderText *textRenderer = static_cast<RenderText *>(r); 0925 if (textRenderer->stringLength() == 0 || !textRenderer->firstTextBox()) { 0926 fprintf(stderr, "%s#text (empty)\n", selected ? "==> " : " "); 0927 return; 0928 } 0929 0930 static const int max = 36; 0931 QString text = DOMString(textRenderer->string()).string(); 0932 int textLength = text.length(); 0933 if (selected) { 0934 int offset = 0; 0935 if (r->node() == start().node()) { 0936 offset = start().offset(); 0937 } else if (r->node() == end().node()) { 0938 offset = end().offset(); 0939 } 0940 0941 int pos; 0942 const InlineTextBox *box = textRenderer->findInlineTextBox(offset, pos); 0943 text = text.mid(box->m_start, box->m_len); 0944 0945 QString show; 0946 int mid = max / 2; 0947 int caret = 0; 0948 0949 // text is shorter than max 0950 if (textLength < max) { 0951 show = text; 0952 caret = pos; 0953 } 0954 0955 // too few characters to left 0956 else if (pos - mid < 0) { 0957 show = text.left(max - 3) + "..."; 0958 caret = pos; 0959 } 0960 0961 // enough characters on each side 0962 else if (pos - mid >= 0 && pos + mid <= textLength) { 0963 show = "..." + text.mid(pos - mid + 3, max - 6) + "..."; 0964 caret = mid; 0965 } 0966 0967 // too few characters on right 0968 else { 0969 show = "..." + text.right(max - 3); 0970 caret = pos - (textLength - show.length()); 0971 } 0972 0973 show = show.replace("\n", " "); 0974 show = show.replace("\r", " "); 0975 fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.toLatin1().data(), pos); 0976 fprintf(stderr, " "); 0977 for (int i = 0; i < caret; i++) { 0978 fprintf(stderr, " "); 0979 } 0980 fprintf(stderr, "^\n"); 0981 } else { 0982 if ((int)text.length() > max) { 0983 text = text.left(max - 3) + "..."; 0984 } else { 0985 text = text.left(max); 0986 } 0987 fprintf(stderr, " #text : \"%s\"\n", text.toLatin1().data()); 0988 } 0989 } 0990 } 0991 0992 void Selection::debugPosition() const 0993 { 0994 if (!start().node()) { 0995 return; 0996 } 0997 0998 //static int context = 5; 0999 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