Ticket #15714 (confirmed defect)
Caret jumping problem in wxRichTextCtrl text selection
|Reported by:||ikamakj||Owned by:|
When text is selected by mouse, the end of the selection jumps abruptly to the last character whenever the mouse goes outside the text control. The problem is present in 2.9.5 but was not in 2.8.10, it is caused by the changes done to hit-testing logic in order to handle sub-objects. The following lines have been added at the beginning of wxRichTextParagraph::HitTest():
if (GetParent() && GetParent()->wxRichTextObject::HitTest(dc, context,
pt, tmpPos, & tempObj, & tempContextObj, flags) == wxRICHTEXT_HITTEST_NONE)
My observation is from the simple case when there are no nested objects but paragraphs are direct children of wxRichTextBuffer. When the mouse is outside the control, the above code causes wxRichTextParagraph::HitTest() to return wxRICHTEXT_HITTEST_NONE. This return value eventually propagates to wxRichTextBuffer::HitTest(), which thus sets textPosition to be the end position, causing the caret to jump to the end in wxRichTextCtrl::OnMoveMouse().
The jumping can be avoided by adding an extra test for this situation at the end of OnMoveMouse():
if (hitObj && m_dragging && hit != wxRICHTEXT_HITTEST_NONE &&
m_selectionState == wxRichTextCtrlSelectionState_Normal
&& (distance > 4)
// added line:
&& (! (hitObj == (& m_buffer) && ((hit & wxRICHTEXT_HITTEST_OUTSIDE) != 0)))
SetCaretPositionAfterClick(container, position, hit, true /* extend selection */);
This same test is in the arrow key handling (function MoveDown(), with comment "outside the buffer counts as 'do nothing'").
However, this is not a full solution because it only prevents the selection from jumping to the end; the selection still stops following the mouse position as the mouse goes outside, in contrast to common word processors and wxWidgets 2.8. In 2.8 the selection did follow the mouse because the latter part of wxRichTextParagraph::HitTest(), which scans lines, was executed even when the point was outside. (In 2.8 there was, however, an asymmetry: when the mouse was moved above the first line, the selection followed the mouse X coordinate, but when the mouse was moved below the last line, the selection was extended to the end of the text regardless of the X coordinate. This was caused by wxRichTextBuffer::HitTest() setting the position to end in case wxRICHTEXT_HITTEST_NONE, as mentioned above.)
Returning to the 2.8 code in wxRichTextParagraph::HitTest(), i.e., removing the added first lines, would make the mouse behave like in 2.8, but this is not a working solution even in the case of no nested objects, because MoveDown() would then behave incorrectly. When the caret is on the first line and the up arrow key is pressed, wxRichTextParagraph::HitTest() is called with a point that is above the first line (usually negative Y value). The 2.8 logic would then treat the point as belonging to the first line (this is the reason why the selection used to follow the X coordinate even when Y was outside, see above). But this would eventually make MoveDown() to incorrectly return true, even though the caret could not be moved upwards. With the 2.8 logic, MoveDown() would also incorrectly remove the whole selection if the user was selecting text with Shift+up arrow and the first line was already reached.
So there seems to be the fundamental problem that hit-testing cannot be done in the same way for selection by mouse and caret movements using keyboard. Selection by mouse should follow the mouse position even when the mouse goes outside, but keyboard handling should do nothing when the caret is already at top or bottom.