+2005-01-31 Darin Adler <darin@apple.com>
+
+ Reviewed by Harrison.
+
+ - fixed <rdar://problem/3980066> Double-click on single character moves insertion point to previous line
+
+ * khtml/khtml_part.cpp:
+ (KHTMLPart::selectClosestWordFromMouseEvent): Set affinity too.
+ (KHTMLPart::handleMousePressEventTripleClick): Ditto.
+ (KHTMLPart::handleMouseMoveEventSelection): Ditto.
+ (KHTMLPart::khtmlMouseReleaseEvent): Ditto.
+
+ * khtml/editing/selection.cpp:
+ (khtml::Selection::modifyExtendingRightForward): Use endOfLine and endOfDocument.
+ (khtml::Selection::modifyMovingRightForward): Ditto.
+ (khtml::Selection::modifyExtendingLeftBackward): Use startOfLine and startOfDocument.
+ (khtml::Selection::modifyMovingLeftBackward): Ditto.
+ (khtml::Selection::validate): Rewrote the section that handles double-click. Two main fixes: 1) use isStartOfLine to
+ check for another case where we want to select the word to the right, and 2) use isEndOfParagraph, which seems
+ to work correctly in cases where isLastVisiblePositionInParagraph is giving the wrong answer. Also changed the line
+ code to use startOfLine/endOfLine and the document code to use startOfDocument/endOfDocument.
+
2005-01-31 Darin Adler <darin@apple.com>
Reviewed by Harrison.
namespace khtml {
-static Selection selectionForLine(const Position &position, EAffinity affinity);
-
Selection::Selection()
{
init();
pos = nextLinePosition(pos, m_affinity, xPosForVerticalArrowNavigation(EXTENT));
break;
case LINE_BOUNDARY:
- pos = VisiblePosition(selectionForLine(m_end, m_affinity).end());
+ pos = endOfLine(VisiblePosition(m_end), m_affinity);
break;
case PARAGRAPH_BOUNDARY:
pos = endOfParagraph(VisiblePosition(m_end));
break;
- case DOCUMENT_BOUNDARY: {
- NodeImpl *de = m_start.node()->getDocument()->documentElement();
- pos = VisiblePosition(de, de ? de->childNodeCount() : 0);
+ case DOCUMENT_BOUNDARY:
+ pos = endOfDocument(pos);
break;
- }
}
return pos;
}
break;
}
case LINE_BOUNDARY:
- pos = VisiblePosition(selectionForLine(m_end, m_affinity).end());
+ pos = endOfLine(VisiblePosition(m_end), m_affinity);
break;
case PARAGRAPH_BOUNDARY:
pos = endOfParagraph(VisiblePosition(m_end));
break;
- case DOCUMENT_BOUNDARY: {
- NodeImpl *de = m_start.node()->getDocument()->documentElement();
- pos = VisiblePosition(de, de ? de->childNodeCount() : 0);
+ case DOCUMENT_BOUNDARY:
+ pos = endOfDocument(VisiblePosition(m_end));
break;
- }
}
return pos;
}
pos = previousLinePosition(pos, m_affinity, xPosForVerticalArrowNavigation(EXTENT));
break;
case LINE_BOUNDARY:
- pos = VisiblePosition(selectionForLine(m_start, m_affinity).start());
+ pos = startOfLine(VisiblePosition(m_start), m_affinity);
break;
case PARAGRAPH_BOUNDARY:
pos = startOfParagraph(VisiblePosition(m_start));
break;
case DOCUMENT_BOUNDARY:
- pos = VisiblePosition(m_start.node()->getDocument()->documentElement(), 0);
+ pos = startOfDocument(pos);
break;
}
return pos;
pos = previousLinePosition(VisiblePosition(m_start), m_affinity, xPosForVerticalArrowNavigation(START, isRange()));
break;
case LINE_BOUNDARY:
- pos = VisiblePosition(selectionForLine(m_start, m_affinity).start());
+ pos = startOfLine(VisiblePosition(m_start), m_affinity);
break;
case PARAGRAPH_BOUNDARY:
pos = startOfParagraph(VisiblePosition(m_start));
break;
case DOCUMENT_BOUNDARY:
- pos = VisiblePosition(m_start.node()->getDocument()->documentElement(), 0);
+ pos = startOfDocument(VisiblePosition(m_start));
break;
}
return pos;
}
break;
case WORD:
- if (m_baseIsStart) {
- // When double clicking (i.e. isCaret() == true), generally select the previous word. The only time to select the other
- // direction is when double-clicking after a hard line break. The check for a hard line break is "end of paragraph".
- if (isCaret()) {
- VisiblePosition extent = VisiblePosition(m_extent);
- bool atEndOfParagraph = isLastVisiblePositionInParagraph(extent);
-
- EWordSide endSide = !atEndOfParagraph ? LeftWordIfOnBoundary : RightWordIfOnBoundary;
- VisiblePosition wordEnd = endOfWord(extent, endSide);
- m_end = wordEnd.deepEquivalent();
-
- EWordSide startSide = !atEndOfParagraph ? LeftWordIfOnBoundary : RightWordIfOnBoundary;
-
- // atEndOfParagraph reflects hard line break well, except at end-of-document, so we are more careful there
- if (startSide == RightWordIfOnBoundary && wordEnd.next().isNull() && !isFirstVisiblePositionOnLine(wordEnd))
- startSide = LeftWordIfOnBoundary;
- m_start = startOfWord(VisiblePosition(m_base), startSide).deepEquivalent();
- } else {
- m_start = startOfWord(VisiblePosition(m_base)).deepEquivalent();
- m_end = endOfWord(VisiblePosition(m_extent)).deepEquivalent();
- }
+ if (isCaret()) {
+ // When double clicking (i.e. selection is a caret), generally select the previous word.
+ // One exception is double-clicking after a hard line break. The check for a hard line break is "end of paragraph".
+ // Another exception is when double-clicking at the start of a line.
+ // However, the end of the document is an exception and always selects the previous word even though it could be
+ // both the start of a line and after a hard line break.
+ VisiblePosition pos(m_base);
+ EWordSide side = LeftWordIfOnBoundary;
+ if ((isEndOfParagraph(pos) || isStartOfLine(pos, m_affinity)) && !isEndOfDocument(pos))
+ side = RightWordIfOnBoundary;
+ m_start = startOfWord(pos, side).deepEquivalent();
+ m_end = endOfWord(pos, side).deepEquivalent();
+ } else if (m_baseIsStart) {
+ m_start = startOfWord(VisiblePosition(m_base)).deepEquivalent();
+ m_end = endOfWord(VisiblePosition(m_extent)).deepEquivalent();
} else {
m_start = startOfWord(VisiblePosition(m_extent)).deepEquivalent();
m_end = endOfWord(VisiblePosition(m_base)).deepEquivalent();
}
break;
case LINE:
- case LINE_BOUNDARY: {
- Selection baseSelection = *this;
- Selection extentSelection = *this;
- Selection baseLine = selectionForLine(m_base, m_affinity);
- if (baseLine.isCaretOrRange()) {
- baseSelection = baseLine;
- }
- Selection extentLine = selectionForLine(m_extent, m_affinity);
- if (extentLine.isCaretOrRange()) {
- extentSelection = extentLine;
- }
+ case LINE_BOUNDARY:
if (m_baseIsStart) {
- m_start = baseSelection.m_start;
- m_end = extentSelection.m_end;
+ m_start = startOfLine(VisiblePosition(m_base), m_affinity).deepEquivalent();
+ m_end = endOfLine(VisiblePosition(m_extent), m_affinity, IncludeLineBreak).deepEquivalent();
} else {
- m_start = extentSelection.m_start;
- m_end = baseSelection.m_end;
+ m_start = startOfLine(VisiblePosition(m_extent), m_affinity).deepEquivalent();
+ m_end = endOfLine(VisiblePosition(m_base), m_affinity, IncludeLineBreak).deepEquivalent();
}
break;
- }
case PARAGRAPH:
if (m_baseIsStart) {
m_start = startOfParagraph(VisiblePosition(m_base)).deepEquivalent();
m_end = endOfParagraph(VisiblePosition(m_base), IncludeLineBreak).deepEquivalent();
}
break;
- case DOCUMENT_BOUNDARY: {
- NodeImpl *de = m_start.node()->getDocument()->documentElement();
- m_start = VisiblePosition(de, 0).deepEquivalent();
- m_end = VisiblePosition(de, de ? de->childNodeCount() : 0).deepEquivalent();
+ case DOCUMENT_BOUNDARY:
+ m_start = startOfDocument(VisiblePosition(m_base)).deepEquivalent();
+ m_end = endOfDocument(VisiblePosition(m_base)).deepEquivalent();
break;
- }
case PARAGRAPH_BOUNDARY:
if (m_baseIsStart) {
m_start = startOfParagraph(VisiblePosition(m_base)).deepEquivalent();
#endif
}
-static NodeImpl *nodeForInlineBox(const InlineBox *box)
-{
- if (!box || !box->object())
- return 0;
- return box->object()->element();
-}
-
-static Selection selectionForLine(const Position &pos, EAffinity affinity)
-{
- if (pos.isNull())
- return Selection();
-
- RenderObject *renderer = pos.node()->renderer();
- if (!renderer)
- return Selection();
-
- InlineBox *box = renderer->inlineBox(pos.offset(), affinity);
- if (!box)
- return Selection();
-
- RootInlineBox *rootBox = box->root();
- if (!rootBox)
- return Selection();
-
- // Find the start position for this line.
- InlineBox *startBox = rootBox->firstChild();
- NodeImpl *startNode = nodeForInlineBox(startBox);
- if (!startNode)
- return Selection();
- long startOffset = 0;
- if (startBox->isInlineTextBox()) {
- InlineTextBox *startTextBox = static_cast<InlineTextBox *>(startBox);
- startOffset = startTextBox->m_start;
- }
- Position start(startNode, startOffset);
-
- // Find the end position for this line.
- InlineBox *endBox = rootBox->lastChild();
- NodeImpl *endNode = nodeForInlineBox(endBox);
- if (!endNode)
- return Selection();
- long endOffset = 1;
- if (endBox->isInlineTextBox()) {
- InlineTextBox *endTextBox = static_cast<InlineTextBox *>(endBox);
- endOffset = endTextBox->m_start + endTextBox->m_len;
- }
- Position end(endNode, endOffset);
-
- return Selection(start, end);
-}
-
void Selection::debugRenderer(RenderObject *r, bool selected) const
{
if (r->node()->isElementNode()) {
namespace khtml {
-static Selection selectionForLine(const Position &position, EAffinity affinity);
-
Selection::Selection()
{
init();
pos = nextLinePosition(pos, m_affinity, xPosForVerticalArrowNavigation(EXTENT));
break;
case LINE_BOUNDARY:
- pos = VisiblePosition(selectionForLine(m_end, m_affinity).end());
+ pos = endOfLine(VisiblePosition(m_end), m_affinity);
break;
case PARAGRAPH_BOUNDARY:
pos = endOfParagraph(VisiblePosition(m_end));
break;
- case DOCUMENT_BOUNDARY: {
- NodeImpl *de = m_start.node()->getDocument()->documentElement();
- pos = VisiblePosition(de, de ? de->childNodeCount() : 0);
+ case DOCUMENT_BOUNDARY:
+ pos = endOfDocument(pos);
break;
- }
}
return pos;
}
break;
}
case LINE_BOUNDARY:
- pos = VisiblePosition(selectionForLine(m_end, m_affinity).end());
+ pos = endOfLine(VisiblePosition(m_end), m_affinity);
break;
case PARAGRAPH_BOUNDARY:
pos = endOfParagraph(VisiblePosition(m_end));
break;
- case DOCUMENT_BOUNDARY: {
- NodeImpl *de = m_start.node()->getDocument()->documentElement();
- pos = VisiblePosition(de, de ? de->childNodeCount() : 0);
+ case DOCUMENT_BOUNDARY:
+ pos = endOfDocument(VisiblePosition(m_end));
break;
- }
}
return pos;
}
pos = previousLinePosition(pos, m_affinity, xPosForVerticalArrowNavigation(EXTENT));
break;
case LINE_BOUNDARY:
- pos = VisiblePosition(selectionForLine(m_start, m_affinity).start());
+ pos = startOfLine(VisiblePosition(m_start), m_affinity);
break;
case PARAGRAPH_BOUNDARY:
pos = startOfParagraph(VisiblePosition(m_start));
break;
case DOCUMENT_BOUNDARY:
- pos = VisiblePosition(m_start.node()->getDocument()->documentElement(), 0);
+ pos = startOfDocument(pos);
break;
}
return pos;
pos = previousLinePosition(VisiblePosition(m_start), m_affinity, xPosForVerticalArrowNavigation(START, isRange()));
break;
case LINE_BOUNDARY:
- pos = VisiblePosition(selectionForLine(m_start, m_affinity).start());
+ pos = startOfLine(VisiblePosition(m_start), m_affinity);
break;
case PARAGRAPH_BOUNDARY:
pos = startOfParagraph(VisiblePosition(m_start));
break;
case DOCUMENT_BOUNDARY:
- pos = VisiblePosition(m_start.node()->getDocument()->documentElement(), 0);
+ pos = startOfDocument(VisiblePosition(m_start));
break;
}
return pos;
}
break;
case WORD:
- if (m_baseIsStart) {
- // When double clicking (i.e. isCaret() == true), generally select the previous word. The only time to select the other
- // direction is when double-clicking after a hard line break. The check for a hard line break is "end of paragraph".
- if (isCaret()) {
- VisiblePosition extent = VisiblePosition(m_extent);
- bool atEndOfParagraph = isLastVisiblePositionInParagraph(extent);
-
- EWordSide endSide = !atEndOfParagraph ? LeftWordIfOnBoundary : RightWordIfOnBoundary;
- VisiblePosition wordEnd = endOfWord(extent, endSide);
- m_end = wordEnd.deepEquivalent();
-
- EWordSide startSide = !atEndOfParagraph ? LeftWordIfOnBoundary : RightWordIfOnBoundary;
-
- // atEndOfParagraph reflects hard line break well, except at end-of-document, so we are more careful there
- if (startSide == RightWordIfOnBoundary && wordEnd.next().isNull() && !isFirstVisiblePositionOnLine(wordEnd))
- startSide = LeftWordIfOnBoundary;
- m_start = startOfWord(VisiblePosition(m_base), startSide).deepEquivalent();
- } else {
- m_start = startOfWord(VisiblePosition(m_base)).deepEquivalent();
- m_end = endOfWord(VisiblePosition(m_extent)).deepEquivalent();
- }
+ if (isCaret()) {
+ // When double clicking (i.e. selection is a caret), generally select the previous word.
+ // One exception is double-clicking after a hard line break. The check for a hard line break is "end of paragraph".
+ // Another exception is when double-clicking at the start of a line.
+ // However, the end of the document is an exception and always selects the previous word even though it could be
+ // both the start of a line and after a hard line break.
+ VisiblePosition pos(m_base);
+ EWordSide side = LeftWordIfOnBoundary;
+ if ((isEndOfParagraph(pos) || isStartOfLine(pos, m_affinity)) && !isEndOfDocument(pos))
+ side = RightWordIfOnBoundary;
+ m_start = startOfWord(pos, side).deepEquivalent();
+ m_end = endOfWord(pos, side).deepEquivalent();
+ } else if (m_baseIsStart) {
+ m_start = startOfWord(VisiblePosition(m_base)).deepEquivalent();
+ m_end = endOfWord(VisiblePosition(m_extent)).deepEquivalent();
} else {
m_start = startOfWord(VisiblePosition(m_extent)).deepEquivalent();
m_end = endOfWord(VisiblePosition(m_base)).deepEquivalent();
}
break;
case LINE:
- case LINE_BOUNDARY: {
- Selection baseSelection = *this;
- Selection extentSelection = *this;
- Selection baseLine = selectionForLine(m_base, m_affinity);
- if (baseLine.isCaretOrRange()) {
- baseSelection = baseLine;
- }
- Selection extentLine = selectionForLine(m_extent, m_affinity);
- if (extentLine.isCaretOrRange()) {
- extentSelection = extentLine;
- }
+ case LINE_BOUNDARY:
if (m_baseIsStart) {
- m_start = baseSelection.m_start;
- m_end = extentSelection.m_end;
+ m_start = startOfLine(VisiblePosition(m_base), m_affinity).deepEquivalent();
+ m_end = endOfLine(VisiblePosition(m_extent), m_affinity, IncludeLineBreak).deepEquivalent();
} else {
- m_start = extentSelection.m_start;
- m_end = baseSelection.m_end;
+ m_start = startOfLine(VisiblePosition(m_extent), m_affinity).deepEquivalent();
+ m_end = endOfLine(VisiblePosition(m_base), m_affinity, IncludeLineBreak).deepEquivalent();
}
break;
- }
case PARAGRAPH:
if (m_baseIsStart) {
m_start = startOfParagraph(VisiblePosition(m_base)).deepEquivalent();
m_end = endOfParagraph(VisiblePosition(m_base), IncludeLineBreak).deepEquivalent();
}
break;
- case DOCUMENT_BOUNDARY: {
- NodeImpl *de = m_start.node()->getDocument()->documentElement();
- m_start = VisiblePosition(de, 0).deepEquivalent();
- m_end = VisiblePosition(de, de ? de->childNodeCount() : 0).deepEquivalent();
+ case DOCUMENT_BOUNDARY:
+ m_start = startOfDocument(VisiblePosition(m_base)).deepEquivalent();
+ m_end = endOfDocument(VisiblePosition(m_base)).deepEquivalent();
break;
- }
case PARAGRAPH_BOUNDARY:
if (m_baseIsStart) {
m_start = startOfParagraph(VisiblePosition(m_base)).deepEquivalent();
#endif
}
-static NodeImpl *nodeForInlineBox(const InlineBox *box)
-{
- if (!box || !box->object())
- return 0;
- return box->object()->element();
-}
-
-static Selection selectionForLine(const Position &pos, EAffinity affinity)
-{
- if (pos.isNull())
- return Selection();
-
- RenderObject *renderer = pos.node()->renderer();
- if (!renderer)
- return Selection();
-
- InlineBox *box = renderer->inlineBox(pos.offset(), affinity);
- if (!box)
- return Selection();
-
- RootInlineBox *rootBox = box->root();
- if (!rootBox)
- return Selection();
-
- // Find the start position for this line.
- InlineBox *startBox = rootBox->firstChild();
- NodeImpl *startNode = nodeForInlineBox(startBox);
- if (!startNode)
- return Selection();
- long startOffset = 0;
- if (startBox->isInlineTextBox()) {
- InlineTextBox *startTextBox = static_cast<InlineTextBox *>(startBox);
- startOffset = startTextBox->m_start;
- }
- Position start(startNode, startOffset);
-
- // Find the end position for this line.
- InlineBox *endBox = rootBox->lastChild();
- NodeImpl *endNode = nodeForInlineBox(endBox);
- if (!endNode)
- return Selection();
- long endOffset = 1;
- if (endBox->isInlineTextBox()) {
- InlineTextBox *endTextBox = static_cast<InlineTextBox *>(endBox);
- endOffset = endTextBox->m_start + endTextBox->m_len;
- }
- Position end(endNode, endOffset);
-
- return Selection(start, end);
-}
-
void Selection::debugRenderer(RenderObject *r, bool selected) const
{
if (r->node()->isElementNode()) {
Selection selection;
if (!innerNode.isNull() && innerNode.handle()->renderer() && innerNode.handle()->renderer()->shouldSelect()) {
- Position pos(innerNode.handle()->renderer()->positionForCoordinates(x, y));
+ EAffinity affinity;
+ Position pos(innerNode.handle()->renderer()->positionForCoordinates(x, y, &affinity));
if (pos.isNotNull()) {
selection.moveTo(pos);
+ selection.setAffinity(affinity);
selection.expandUsingGranularity(WORD);
}
}
if (mouse->button() == LeftButton && !innerNode.isNull() && innerNode.handle()->renderer() &&
innerNode.handle()->renderer()->shouldSelect()) {
- Position pos(innerNode.handle()->renderer()->positionForCoordinates(event->x(), event->y()));
+ EAffinity affinity;
+ Position pos(innerNode.handle()->renderer()->positionForCoordinates(event->x(), event->y(), &affinity));
if (pos.isNotNull()) {
selection.moveTo(pos);
+ selection.setAffinity(affinity);
selection.expandUsingGranularity(PARAGRAPH);
}
}
return;
// handle making selection
- Position pos(innerNode.handle()->renderer()->positionForCoordinates(event->x(), event->y()));
+ EAffinity affinity;
+ Position pos(innerNode.handle()->renderer()->positionForCoordinates(event->x(), event->y(), &affinity));
// Don't modify the selection if we're not on a node.
if (pos.isNull())
if (!d->m_beganSelectingText) {
d->m_beganSelectingText = true;
sel.moveTo(pos);
+ sel.setAffinity(affinity);
}
sel.setExtent(pos);
&& d->m_selection.isRange()) {
Selection selection;
NodeImpl *node = d->m_selection.base().node();
- if (node->isContentEditable() && node->renderer())
- selection.moveTo(node->renderer()->positionForCoordinates(event->x(), event->y()));
+ if (node->isContentEditable() && node->renderer()) {
+ EAffinity affinity;
+ Position pos = node->renderer()->positionForCoordinates(event->x(), event->y(), &affinity);
+ selection.moveTo(pos);
+ selection.setAffinity(affinity);
+ }
setSelection(selection);
}