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)
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()