2 * Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #include "selection.h"
32 #include "dom/dom_node.h"
33 #include "dom/dom_string.h"
34 #include "khtml_part.h"
35 #include "khtmlview.h"
36 #include "misc/htmltags.h"
37 #include "rendering/render_object.h"
38 #include "rendering/render_style.h"
39 #include "rendering/render_text.h"
40 #include "visible_position.h"
41 #include "visible_units.h"
42 #include "xml/dom_docimpl.h"
43 #include "xml/dom_elementimpl.h"
44 #include "xml/dom_nodeimpl.h"
45 #include "xml/dom_textimpl.h"
46 #include "xml/dom2_rangeimpl.h"
49 #include "KWQAssertions.h"
51 #define ASSERT(assertion) assert(assertion)
57 using DOM::ElementImpl;
63 using DOM::StayInBlock;
64 using DOM::DoNotStayInBlock;
68 Selection::Selection()
73 Selection::Selection(const Position &pos, EAffinity affinity)
74 : m_base(pos), m_extent(pos)
80 Selection::Selection(const Range &r, EAffinity baseAffinity, EAffinity extentAffinity)
81 : m_base(startPosition(r)), m_extent(endPosition(r))
87 Selection::Selection(const Position &base, EAffinity baseAffinity, const Position &extent, EAffinity extentAffinity)
88 : m_base(base), m_extent(extent)
94 Selection::Selection(const VisiblePosition &visiblePos)
95 : m_base(visiblePos.position()), m_extent(visiblePos.position())
97 init(visiblePos.affinity());
101 Selection::Selection(const VisiblePosition &base, const VisiblePosition &extent)
102 : m_base(base.position()), m_extent(extent.position())
104 init(base.affinity());
108 Selection::Selection(const Selection &o)
109 : m_base(o.m_base), m_extent(o.m_extent)
110 , m_start(o.m_start), m_end(o.m_end)
111 , m_state(o.m_state), m_affinity(o.m_affinity)
112 , m_baseIsStart(o.m_baseIsStart)
113 , m_needsLayout(o.m_needsLayout)
114 , m_modifyBiasSet(o.m_modifyBiasSet)
116 // Only copy the coordinates over if the other object
117 // has had a layout, otherwise keep the current
118 // coordinates. This prevents drawing artifacts from
119 // remaining when the caret is painted and then moves,
120 // and the old rectangle needs to be repainted.
121 if (!m_needsLayout) {
122 m_caretRect = o.m_caretRect;
123 m_expectedVisibleRect = o.m_expectedVisibleRect;
127 void Selection::init(EAffinity affinity)
129 // FIXME: set extentAffinity
131 m_baseIsStart = true;
132 m_affinity = affinity;
133 m_needsLayout = true;
134 m_modifyBiasSet = false;
137 Selection &Selection::operator=(const Selection &o)
140 m_extent = o.m_extent;
145 m_affinity = o.m_affinity;
147 m_baseIsStart = o.m_baseIsStart;
148 m_needsLayout = o.m_needsLayout;
149 m_modifyBiasSet = o.m_modifyBiasSet;
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_needsLayout) {
157 m_caretRect = o.m_caretRect;
158 m_expectedVisibleRect = o.m_expectedVisibleRect;
164 void Selection::moveTo(const VisiblePosition &pos)
166 // FIXME: use extentAffinity
167 m_affinity = pos.affinity();
168 m_base = pos.deepEquivalent();
169 m_extent = pos.deepEquivalent();
173 void Selection::moveTo(const VisiblePosition &base, const VisiblePosition &extent)
175 // FIXME: use extentAffinity
176 m_affinity = base.affinity();
177 m_base = base.deepEquivalent();
178 m_extent = extent.deepEquivalent();
182 void Selection::moveTo(const Selection &o)
184 // FIXME: copy extentAffinity
185 m_affinity = o.m_affinity;
191 void Selection::moveTo(const Position &pos, EAffinity affinity)
193 // FIXME: use extentAffinity
194 m_affinity = affinity;
200 void Selection::moveTo(const Range &r, EAffinity baseAffinity, EAffinity extentAffinity)
202 // FIXME: use extentAffinity
203 m_affinity = baseAffinity;
204 m_base = startPosition(r);
205 m_extent = endPosition(r);
209 void Selection::moveTo(const Position &base, EAffinity baseAffinity, const Position &extent, EAffinity extentAffinity)
211 // FIXME: use extentAffinity
212 m_affinity = baseAffinity;
218 void Selection::setModifyBias(EAlter alter, EDirection direction)
222 m_modifyBiasSet = false;
225 if (!m_modifyBiasSet) {
226 m_modifyBiasSet = true;
228 // FIXME: right for bidi?
245 VisiblePosition Selection::modifyExtendingRightForward(ETextGranularity granularity)
247 VisiblePosition pos(m_extent, m_affinity);
248 switch (granularity) {
253 pos = nextWordPosition(pos);
256 pos = nextParagraphPosition(pos, xPosForVerticalArrowNavigation(EXTENT));
259 pos = nextLinePosition(pos, xPosForVerticalArrowNavigation(EXTENT));
262 pos = endOfLine(VisiblePosition(m_end, m_affinity));
264 case PARAGRAPH_BOUNDARY:
265 pos = endOfParagraph(VisiblePosition(m_end, m_affinity));
267 case DOCUMENT_BOUNDARY:
268 pos = endOfDocument(pos);
275 VisiblePosition Selection::modifyMovingRightForward(ETextGranularity granularity)
278 switch (granularity) {
281 pos = VisiblePosition(m_end, m_affinity);
283 pos = VisiblePosition(m_extent, m_affinity).next();
286 pos = nextWordPosition(VisiblePosition(m_extent, m_affinity));
289 pos = nextParagraphPosition(VisiblePosition(m_end, m_affinity), xPosForVerticalArrowNavigation(END, isRange()));
292 // down-arrowing from a range selection that ends at the start of a line needs
293 // to leave the selection at that line start (no need to call nextLinePosition!)
294 pos = VisiblePosition(m_end, m_affinity);
295 if (!isRange() || !isStartOfLine(pos))
296 pos = nextLinePosition(pos, xPosForVerticalArrowNavigation(END, isRange()));
300 pos = endOfLine(VisiblePosition(m_end, m_affinity));
302 case PARAGRAPH_BOUNDARY:
303 pos = endOfParagraph(VisiblePosition(m_end, m_affinity));
305 case DOCUMENT_BOUNDARY:
306 pos = endOfDocument(VisiblePosition(m_end, m_affinity));
312 VisiblePosition Selection::modifyExtendingLeftBackward(ETextGranularity granularity)
314 VisiblePosition pos(m_extent, m_affinity);
315 switch (granularity) {
317 pos = pos.previous();
320 pos = previousWordPosition(pos);
323 pos = previousParagraphPosition(pos, xPosForVerticalArrowNavigation(EXTENT));
326 pos = previousLinePosition(pos, xPosForVerticalArrowNavigation(EXTENT));
329 pos = startOfLine(VisiblePosition(m_start, m_affinity));
331 case PARAGRAPH_BOUNDARY:
332 pos = startOfParagraph(VisiblePosition(m_start, m_affinity));
334 case DOCUMENT_BOUNDARY:
335 pos = startOfDocument(pos);
341 VisiblePosition Selection::modifyMovingLeftBackward(ETextGranularity granularity)
344 switch (granularity) {
347 pos = VisiblePosition(m_start, m_affinity);
349 pos = VisiblePosition(m_extent, m_affinity).previous();
352 pos = previousWordPosition(VisiblePosition(m_extent, m_affinity));
355 pos = previousParagraphPosition(VisiblePosition(m_start, m_affinity), xPosForVerticalArrowNavigation(START, isRange()));
358 pos = previousLinePosition(VisiblePosition(m_start, m_affinity), xPosForVerticalArrowNavigation(START, isRange()));
361 pos = startOfLine(VisiblePosition(m_start, m_affinity));
363 case PARAGRAPH_BOUNDARY:
364 pos = startOfParagraph(VisiblePosition(m_start, m_affinity));
366 case DOCUMENT_BOUNDARY:
367 pos = startOfDocument(VisiblePosition(m_start, m_affinity));
373 bool Selection::modify(EAlter alter, EDirection dir, ETextGranularity granularity)
375 setModifyBias(alter, dir);
379 // EDIT FIXME: These need to handle bidi
383 pos = modifyExtendingRightForward(granularity);
385 pos = modifyMovingRightForward(granularity);
390 pos = modifyExtendingLeftBackward(granularity);
392 pos = modifyMovingLeftBackward(granularity);
413 // FIXME: Maybe baseline would be better?
414 static bool caretY(const VisiblePosition &c, int &y)
416 Position p = c.deepEquivalent();
417 NodeImpl *n = p.node();
420 RenderObject *r = p.node()->renderer();
423 QRect rect = r->caretRect(p.offset());
426 y = rect.y() + rect.height() / 2;
430 bool Selection::modify(EAlter alter, int verticalDistance)
432 if (verticalDistance == 0)
435 bool up = verticalDistance < 0;
437 verticalDistance = -verticalDistance;
439 // can dump this UPSTREAM when we have m_extentAffinity
440 m_affinity = UPSTREAM;
441 setModifyBias(alter, up ? BACKWARD : FORWARD);
447 pos = VisiblePosition(up ? m_start : m_end, m_affinity);
448 xPos = xPosForVerticalArrowNavigation(up ? START : END, isRange());
451 pos = VisiblePosition(m_extent, m_affinity);
452 xPos = xPosForVerticalArrowNavigation(EXTENT);
457 if (!caretY(pos, startY))
463 VisiblePosition result;
464 VisiblePosition next;
465 for (VisiblePosition p = pos; ; p = next) {
466 next = (up ? previousLinePosition : nextLinePosition)(p, xPos);
467 if (next.isNull() || next == p)
470 if (!caretY(next, nextY))
474 if (nextY - startY > verticalDistance)
476 if (nextY >= lastY) {
497 bool Selection::expandUsingGranularity(ETextGranularity granularity)
501 validate(granularity);
505 int Selection::xPosForVerticalArrowNavigation(EPositionType type, bool recalc) const
528 KHTMLPart *part = pos.node()->getDocument()->part();
532 if (recalc || part->xPosForVerticalArrowNavigation() == KHTMLPart::NoXPosForVerticalArrowNavigation) {
533 switch (m_affinity) {
535 pos = VisiblePosition(pos, m_affinity).downstreamDeepEquivalent();
538 pos = VisiblePosition(pos, m_affinity).deepEquivalent();
541 x = pos.node()->renderer()->caretRect(pos.offset(), m_affinity).x();
542 part->setXPosForVerticalArrowNavigation(x);
545 x = part->xPosForVerticalArrowNavigation();
550 void Selection::clear()
552 m_affinity = SEL_DEFAULT_AFFINITY;
558 void Selection::setBase(const VisiblePosition &pos)
560 m_affinity = pos.affinity();
561 m_base = pos.deepEquivalent();
565 void Selection::setExtent(const VisiblePosition &pos)
567 // FIXME: Support extentAffinity
568 m_extent = pos.deepEquivalent();
572 void Selection::setBaseAndExtent(const VisiblePosition &base, const VisiblePosition &extent)
574 // FIXME: Support extentAffinity
575 m_affinity = base.affinity();
576 m_base = base.deepEquivalent();
577 m_extent = extent.deepEquivalent();
582 void Selection::setBase(const Position &pos, EAffinity baseAffinity)
584 m_affinity = baseAffinity;
589 void Selection::setExtent(const Position &pos, EAffinity extentAffinity)
591 // FIXME: Support extentAffinity for real
592 m_affinity = extentAffinity;
597 void Selection::setBaseAndExtent(const Position &base, EAffinity baseAffinity, const Position &extent, EAffinity extentAffinity)
599 // FIXME: extentAffinity
600 m_affinity = baseAffinity;
606 void Selection::setNeedsLayout(bool flag)
608 m_needsLayout = flag;
611 Range Selection::toRange() const
616 // Make sure we have an updated layout since this function is called
617 // in the course of running edit commands which modify the DOM.
618 // Failing to call this can result in equivalentXXXPosition calls returning
619 // incorrect results.
620 m_start.node()->getDocument()->updateLayout();
624 // If the selection is a caret, move the range start upstream. This helps us match
625 // the conventions of text editors tested, which make style determinations based
626 // on the character before the caret, if any.
627 s = m_start.upstream(StayInBlock).equivalentRangeCompliantPosition();
631 // If the selection is a range, select the minimum range that encompasses the selection.
632 // Again, this is to match the conventions of text editors tested, which make style
633 // determinations based on the first character of the selection.
634 // For instance, this operation helps to make sure that the "X" selected below is the
635 // only thing selected. The range should not be allowed to "leak" out to the end of the
636 // previous text node, or to the beginning of the next text node, each of which has a
639 // On a treasure map, <b>X</b> marks the spot.
643 s = m_start.downstream(StayInBlock);
644 e = m_end.upstream(StayInBlock);
645 if (RangeImpl::compareBoundaryPoints(s.node(), s.offset(), e.node(), e.offset()) > 0) {
646 // Make sure the start is before the end.
647 // The end can wind up before the start if collapsed whitespace is the only thing selected.
652 s = s.equivalentRangeCompliantPosition();
653 e = e.equivalentRangeCompliantPosition();
656 // Use this roundabout way of creating the Range in order to have defined behavior
657 // when there is a DOM exception.
658 int exceptionCode = 0;
659 Range result(s.node()->getDocument());
660 RangeImpl *handle = result.handle();
662 handle->setStart(s.node(), s.offset(), exceptionCode);
664 ERROR("Exception setting Range start from Selection: %d", exceptionCode);
667 handle->setEnd(e.node(), e.offset(), exceptionCode);
669 ERROR("Exception setting Range end from Selection: %d", exceptionCode);
675 void Selection::layout()
677 if (isNone() || !m_start.node()->inDocument() || !m_end.node()->inDocument()) {
678 m_caretRect = QRect();
679 m_expectedVisibleRect = QRect();
683 m_start.node()->getDocument()->updateRendering();
686 Position pos = m_start;
687 switch (m_affinity) {
689 pos = VisiblePosition(m_start, m_affinity).downstreamDeepEquivalent();
692 pos = VisiblePosition(m_start, m_affinity).deepEquivalent();
695 if (pos.isNotNull()) {
696 ASSERT(pos.node()->renderer());
697 m_caretRect = pos.node()->renderer()->caretRect(pos.offset(), m_affinity);
698 m_expectedVisibleRect = m_caretRect;
701 m_caretRect = QRect();
702 m_expectedVisibleRect = QRect();
706 // Calculate which position to use based on whether the base is the start.
707 // We want the position, start or end, that was calculated using the extent.
708 // This makes the selection follow the extent position while scrolling as a
709 // result of arrow navigation.
711 // Note: no need to get additional help from VisiblePosition. The m_start and
712 // m_end positions should already be visible, and we're only interested in
713 // a rectangle for m_expectedVisibleRect, hence affinity is not a factor
714 // like it is when drawing a caret.
716 Position pos = m_baseIsStart ? m_end : m_start;
717 ASSERT(pos.node()->renderer());
718 m_expectedVisibleRect = pos.node()->renderer()->caretRect(pos.offset(), m_affinity);
719 m_caretRect = QRect();
722 m_needsLayout = false;
725 QRect Selection::caretRect() const
728 const_cast<Selection *>(this)->layout();
734 QRect Selection::expectedVisibleRect() const
737 const_cast<Selection *>(this)->layout();
740 return m_expectedVisibleRect;
743 QRect Selection::caretRepaintRect() const
745 // FIXME: Add one pixel of slop on each side to make sure we don't leave behind artifacts.
746 QRect r = caretRect();
749 return QRect(r.left() - 1, r.top() - 1, r.width() + 2, r.height() + 2);
752 void Selection::needsCaretRepaint()
757 if (!m_start.node()->getDocument())
760 KHTMLView *v = m_start.node()->getDocument()->view();
765 // repaint old position and calculate new position
766 v->updateContents(caretRepaintRect(), false);
769 // EDIT FIXME: This is an unfortunate hack.
770 // Basically, we can't trust this layout position since we
771 // can't guarantee that the check to see if we are in unrendered
772 // content will work at this point. We may have to wait for
773 // a layout and re-render of the document to happen. So, resetting this
774 // flag will cause another caret layout to happen the first time
775 // that we try to paint the caret after this call. That one will work since
776 // it happens after the document has accounted for any editing
777 // changes which may have been done.
778 // And, we need to leave this layout here so the caret moves right
779 // away after clicking.
780 m_needsLayout = true;
782 v->updateContents(caretRepaintRect(), false);
785 void Selection::paintCaret(QPainter *p, const QRect &rect)
787 if (m_state != CARET)
793 if (m_caretRect.isValid())
794 p->fillRect(m_caretRect & rect, QBrush());
797 void Selection::validate(ETextGranularity granularity)
799 // Move the selection to rendered positions, if possible.
800 Position originalBase(m_base);
801 bool baseAndExtentEqual = m_base == m_extent;
802 bool updatedLayout = false;
803 if (m_base.isNotNull()) {
804 m_base.node()->getDocument()->updateLayout();
805 updatedLayout = true;
806 m_base = VisiblePosition(m_base, m_affinity).deepEquivalent();
807 if (baseAndExtentEqual)
810 if (m_extent.isNotNull() && !baseAndExtentEqual) {
812 m_extent.node()->getDocument()->updateLayout();
813 m_extent = VisiblePosition(m_extent, m_affinity).deepEquivalent();
816 // Make sure we do not have a dangling start or end
817 if (m_base.isNull() && m_extent.isNull()) {
818 // Move the position to the enclosingBlockFlowElement of the original base, if possible.
819 // This has the effect of flashing the caret somewhere when a rendered position for
820 // the base and extent cannot be found.
821 if (originalBase.isNotNull()) {
822 Position pos(originalBase.node()->enclosingBlockFlowElement(), 0);
827 // We have no position to work with. See if the BODY element of the page
828 // is contentEditable. If it is, put the caret there.
829 //NodeImpl *node = document()
833 m_baseIsStart = true;
835 else if (m_base.isNull()) {
837 m_baseIsStart = true;
839 else if (m_extent.isNull()) {
841 m_baseIsStart = true;
844 m_baseIsStart = RangeImpl::compareBoundaryPoints(m_base.node(), m_base.offset(), m_extent.node(), m_extent.offset()) <= 0;
850 // calculate the correct start and end positions
851 switch (granularity) {
862 // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary).
863 // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in
864 // the document, select that last word (LeftWordIfOnBoundary).
865 // Edge case: If the caret is after the last word in a paragraph, select from the the end of the
866 // last word to the line break (also RightWordIfOnBoundary);
867 VisiblePosition start = m_baseIsStart ? VisiblePosition(m_base, m_affinity) : VisiblePosition(m_extent, m_affinity);
868 VisiblePosition end = m_baseIsStart ? VisiblePosition(m_extent, m_affinity) : VisiblePosition(m_base, m_affinity);
869 EWordSide side = RightWordIfOnBoundary;
870 if (isEndOfDocument(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start)))
871 side = LeftWordIfOnBoundary;
872 m_start = startOfWord(start, side).deepEquivalent();
873 side = RightWordIfOnBoundary;
874 if (isEndOfDocument(end) || (isEndOfLine(end) && !isStartOfLine(end) && !isEndOfParagraph(end)))
875 side = LeftWordIfOnBoundary;
876 m_end = endOfWord(end, side).deepEquivalent();
883 m_start = startOfLine(VisiblePosition(m_base, m_affinity)).deepEquivalent();
884 m_end = endOfLine(VisiblePosition(m_extent, m_affinity), IncludeLineBreak).deepEquivalent();
886 m_start = startOfLine(VisiblePosition(m_extent, m_affinity)).deepEquivalent();
887 m_end = endOfLine(VisiblePosition(m_base, m_affinity), IncludeLineBreak).deepEquivalent();
892 VisiblePosition pos(m_base, m_affinity);
893 if (isStartOfLine(pos) && isEndOfDocument(pos))
894 pos = pos.previous();
895 m_start = startOfParagraph(pos).deepEquivalent();
896 m_end = endOfParagraph(VisiblePosition(m_extent, m_affinity), IncludeLineBreak).deepEquivalent();
898 m_start = startOfParagraph(VisiblePosition(m_extent, m_affinity)).deepEquivalent();
899 m_end = endOfParagraph(VisiblePosition(m_base, m_affinity), IncludeLineBreak).deepEquivalent();
902 case DOCUMENT_BOUNDARY:
903 m_start = startOfDocument(VisiblePosition(m_base, m_affinity)).deepEquivalent();
904 m_end = endOfDocument(VisiblePosition(m_base, m_affinity)).deepEquivalent();
906 case PARAGRAPH_BOUNDARY:
908 m_start = startOfParagraph(VisiblePosition(m_base, m_affinity)).deepEquivalent();
909 m_end = endOfParagraph(VisiblePosition(m_extent, m_affinity)).deepEquivalent();
911 m_start = startOfParagraph(VisiblePosition(m_extent, m_affinity)).deepEquivalent();
912 m_end = endOfParagraph(VisiblePosition(m_base, m_affinity)).deepEquivalent();
918 if (m_start.isNull()) {
919 ASSERT(m_end.isNull());
922 else if (m_start == m_end || m_start.upstream(StayInBlock) == m_end.upstream(StayInBlock)) {
927 // "Constrain" the selection to be the smallest equivalent range of nodes.
928 // This is a somewhat arbitrary choice, but experience shows that it is
929 // useful to make to make the selection "canonical" (if only for
930 // purposes of comparing selections). This is an ideal point of the code
931 // to do this operation, since all selection changes that result in a RANGE
932 // come through here before anyone uses it.
933 m_start = m_start.downstream(StayInBlock);
934 m_end = m_end.upstream(StayInBlock);
937 m_needsLayout = true;
944 void Selection::debugRenderer(RenderObject *r, bool selected) const
946 if (r->node()->isElementNode()) {
947 ElementImpl *element = static_cast<ElementImpl *>(r->node());
948 fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element->tagName().string().latin1());
950 else if (r->isText()) {
951 RenderText *textRenderer = static_cast<RenderText *>(r);
952 if (textRenderer->stringLength() == 0 || !textRenderer->firstTextBox()) {
953 fprintf(stderr, "%s#text (empty)\n", selected ? "==> " : " ");
957 static const int max = 36;
958 QString text = DOMString(textRenderer->string()).string();
959 int textLength = text.length();
962 if (r->node() == m_start.node())
963 offset = m_start.offset();
964 else if (r->node() == m_end.node())
965 offset = m_end.offset();
968 InlineTextBox *box = textRenderer->findNextInlineTextBox(offset, pos);
969 text = text.mid(box->m_start, box->m_len);
975 // text is shorter than max
976 if (textLength < max) {
981 // too few characters to left
982 else if (pos - mid < 0) {
983 show = text.left(max - 3) + "...";
987 // enough characters on each side
988 else if (pos - mid >= 0 && pos + mid <= textLength) {
989 show = "..." + text.mid(pos - mid + 3, max - 6) + "...";
993 // too few characters on right
995 show = "..." + text.right(max - 3);
996 caret = pos - (textLength - show.length());
999 show.replace('\n', ' ');
1000 show.replace('\r', ' ');
1001 fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.latin1(), pos);
1002 fprintf(stderr, " ");
1003 for (int i = 0; i < caret; i++)
1004 fprintf(stderr, " ");
1005 fprintf(stderr, "^\n");
1008 if ((int)text.length() > max)
1009 text = text.left(max - 3) + "...";
1011 text = text.left(max);
1012 fprintf(stderr, " #text : \"%s\"\n", text.latin1());
1017 void Selection::debugPosition() const
1019 if (!m_start.node())
1022 //static int context = 5;
1024 //RenderObject *r = 0;
1026 fprintf(stderr, "Selection =================\n");
1028 if (m_start == m_end) {
1029 Position pos = m_start;
1030 Position upstream = pos.upstream(DoNotStayInBlock);
1031 Position downstream = pos.downstream(DoNotStayInBlock);
1032 fprintf(stderr, "upstream: %s %p:%ld\n", upstream.node()->nodeName().string().latin1(), upstream.node(), upstream.offset());
1033 fprintf(stderr, "pos: %s %p:%ld\n", pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
1034 fprintf(stderr, "downstream: %s %p:%ld\n", downstream.node()->nodeName().string().latin1(), downstream.node(), downstream.offset());
1037 Position pos = m_start;
1038 Position upstream = pos.upstream(DoNotStayInBlock);
1039 Position downstream = pos.downstream(DoNotStayInBlock);
1040 fprintf(stderr, "upstream: %s %p:%ld\n", upstream.node()->nodeName().string().latin1(), upstream.node(), upstream.offset());
1041 fprintf(stderr, "start: %s %p:%ld\n", pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
1042 fprintf(stderr, "downstream: %s %p:%ld\n", downstream.node()->nodeName().string().latin1(), downstream.node(), downstream.offset());
1043 fprintf(stderr, "-----------------------------------\n");
1045 upstream = pos.upstream(DoNotStayInBlock);
1046 downstream = pos.downstream(DoNotStayInBlock);
1047 fprintf(stderr, "upstream: %s %p:%ld\n", upstream.node()->nodeName().string().latin1(), upstream.node(), upstream.offset());
1048 fprintf(stderr, "end: %s %p:%ld\n", pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
1049 fprintf(stderr, "downstream: %s %p:%ld\n", downstream.node()->nodeName().string().latin1(), downstream.node(), downstream.offset());
1050 fprintf(stderr, "-----------------------------------\n");
1055 r = m_start.node()->renderer();
1056 for (int i = 0; i < context; i++, back++) {
1057 if (r->previousRenderer())
1058 r = r->previousRenderer();
1062 for (int i = 0; i < back; i++) {
1063 debugRenderer(r, false);
1064 r = r->nextRenderer();
1068 fprintf(stderr, "\n");
1070 if (m_start.node() == m_end.node())
1071 debugRenderer(m_start.node()->renderer(), true);
1073 for (r = m_start.node()->renderer(); r && r != m_end.node()->renderer(); r = r->nextRenderer())
1074 debugRenderer(r, true);
1076 fprintf(stderr, "\n");
1078 r = m_end.node()->renderer();
1079 for (int i = 0; i < context; i++) {
1080 if (r->nextRenderer()) {
1081 r = r->nextRenderer();
1082 debugRenderer(r, false);
1089 fprintf(stderr, "================================\n");
1093 #define FormatBufferSize 1024
1094 void Selection::formatForDebugger(char *buffer, unsigned length) const
1103 char s[FormatBufferSize];
1105 m_start.formatForDebugger(s, FormatBufferSize);
1108 m_end.formatForDebugger(s, FormatBufferSize);
1112 strncpy(buffer, result.string().latin1(), length - 1);
1114 #undef FormatBufferSize