TextCtrl: Is there a way to select some text while putting caret in the front of selection>

Hello, I am one of NVDA developers and I have a potentially noob question regarding wxPython.
I have a TextCtrl with some text in it, and I am trying to select part of that text in such a way that caret stays at the front of selection. And I can’t seem to find a way to do so.

  • SetSelection() method always puts caret behind, not no matter the order of its arguments;
  • SetInsertionPoint() removes current selection.
  • SEnding EM_SETSEL message also puts caret behind selection regardless of the order of its arguments.
    Am I missing anything or is that really impossible?
    Thanks!
    P.S> If anyone is interested as to why do I need to put caret in the front of selection: I am trying to improve word navigation and word selection for NVDA users. In particular I would like to have many customizable word definitions. Som word definitions would go inside longCamelCaseIdentifiers. Word navigation so far works beautifully, but I am struggling with word selection. If a user selects text backwards - e.g. with control+shift+LeftArrow, I need to put caret in front of selection - otherwise it wouldn’t play nice with other selection commands.

See below for the relevant sources from wxWidgets.

SetInsertionPoint(pos) does call DoSetSelection(pos,pos) which does send an EM_SETSEL.
So, I would guess that this is a Windows behaviour and there is only a selection or an insertion point which is actually a zero-length selection.

Your only chance is probably to catch the point where the user hits a key and then decide how to handle this.

from src/msw/textentry.cpp, lines 757ff:

// ----------------------------------------------------------------------------
// insertion point and selection
// ----------------------------------------------------------------------------

void wxTextEntry::SetInsertionPoint(long pos)
{
    // calling DoSetSelection(-1, -1) would select everything which is not what
    // we want here
    if ( pos == -1 )
        pos = GetLastPosition();

    // be careful to call DoSetSelection() which is overridden in wxTextCtrl
    // and not just SetSelection() here
    DoSetSelection(pos, pos);
}

void wxTextEntry::DoSetSelection(long from, long to, int WXUNUSED(flags))
{
    // if from and to are both -1, it means (in wxWidgets) that all text should
    // be selected, translate this into Windows convention
    if ( (from == -1) && (to == -1) )
    {
        from = 0;
    }

    ::SendMessage(GetEditHwnd(), EM_SETSEL, from, to);
}

See also EM_SETSEL documentation at EM_SETSEL message (Winuser.h) - Win32 apps | Microsoft Learn

Edit controls: The control displays a flashing caret at the end position regardless of the relative values of start and end.

I’ve not found a way to do what you want with the TextCtrl or RichTextCtrl.

However, the code below, using the StyledTextCtrl, appears to be able to select a word and place the caret at the start of the word.

import wx
from wx.stc import StyledTextCtrl

class MyFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)
        self.SetSize((400, 300))
        self.SetTitle("Test StyledTextCtrl")
        self.panel = wx.Panel(self, wx.ID_ANY)
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.stc = StyledTextCtrl(self.panel, wx.ID_ANY)
        sizer.Add(self.stc, 1, wx.EXPAND, 0)
        self.panel.SetSizer(sizer)
        self.Layout()

        self.stc.SetMarginWidth(1, 0)
        self.stc.SetUseHorizontalScrollBar(0)
        self.stc.SetValue("Red Green Blue Yellow")
        self.stc.SetSelectionStart(9)
        self.stc.WordLeftExtend()

if __name__ == "__main__":
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

Tested using wxPython 4.2.1 + Python 3.10.12 + Linux Mint 21.3.

The code below appears to work for the RichTextCtrl:

import wx
import wx.richtext as rt

class MyFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)
        self.SetSize((400, 300))
        self.SetTitle("Test RichTextCtrl")
        self.panel = wx.Panel(self, wx.ID_ANY)
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.rtc = rt.RichTextCtrl(self.panel, wx.ID_ANY)
        sizer.Add(self.rtc, 1, wx.EXPAND, 0)
        self.panel.SetSizer(sizer)
        self.Layout()

        self.rtc.SetValue("Red Green Blue Yellow")
        self.rtc.SetInsertionPoint(9)
        # Move caret left by length of the word
        self.rtc.MoveLeft(5, rt.RICHTEXT_SHIFT_DOWN)

if __name__ == "__main__":
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

Tested using wxPython 4.2.1 + Python 3.10.12 + Linux Mint 21.3

from the demo (more or less) :wink:

import wx
CARET = '^'

class Gui(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)
        self.SetTitle("Caret at Selection")

        txt = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
        def evt_txt_enter(_):
            ins, _ = txt.GetSelection()
            text = txt.GetValue()
            text = ''.join((text[:ins], CARET, text[ins:]))
            txt.ChangeValue(text)
        txt.Bind(wx.EVT_TEXT_ENTER, evt_txt_enter)

        txt.ChangeValue("Red Green Blue Yellow")
        self.Layout()

app = wx.App()
Gui(None).Show()
app.MainLoop()