How to detect character style in RichTextCtrl?

Hi,

I am having with using different character styles in RichTextCtrl. Run the example below to see what I am describing in python3 wxpython 4.1.0.

  1. Problem:
    I need to be able to detect when the caret is in a url style but I am not able to find a good way of doing that.
    The function in the example below kind of works, but reports wrongly at the beginning of the link especially of there is no more text after the link.
    Try moving the caret over the link and observe the output in console and the remove all text after the link and try again.
    Notice that current style can be taken from the RichTextStyleComboCtrl but that always reports paragraph. I have not been able to get this or the other style controls work with character styles.

Is there a way to get it to work so I would not need to calculate the style in a method like that?

  1. Problem
    Remove all text behind the link and start typing from inside the link. There is no way you can stop writing a link. Pressing Enter, Shift+Enter or even deleting everything does not help. You are stuck in the url style forever. :smiley:
    This might be solved by detecting you are in a url and giving you a button that would stop the style. I am quite sure there will be other problems with that, like for example what happens with the other half of the url, but for start the detection would have to work reliably.

Thanks

import wx
import wx.richtext as rt


class RichTextFrame(wx.Frame):
    def __init__(self, *args, **kw):
        wx.Frame.__init__(self, *args, **kw)
        self.rtc = rt.RichTextCtrl(self, style=wx.VSCROLL | wx.HSCROLL | wx.NO_BORDER)
        self.sizer = wx.BoxSizer(wx.VERTICAL)

        # Create style stylesheet and control
        self._stylesheet = rt.RichTextStyleSheet()
        self._stylesheet.SetName('Stylesheet')

        self.style_control = rt.RichTextStyleComboCtrl(self, -1)
        self.rtc.SetStyleSheet(self._stylesheet)
        self.style_control.SetRichTextCtrl(self.rtc)
        self.style_control.SetStyleSheet(self._stylesheet)

        self.sizer.Add(self.rtc, 1, flag=wx.EXPAND)
        self.sizer.Add(self.style_control)
        self.SetSizer(self.sizer)

        self._create_styles()
        self._insert_sample_text()
        self.rtc.Bind(wx.EVT_KEY_UP, self.on_keypress, self.rtc)

    def _create_styles(self) -> None:
        """
        Create styles for rich text control.
        :return: None
        """
        # Normal style
        stl_paragraph: rt.RichTextAttr = self.rtc.GetDefaultStyleEx()
        stl_paragraph.SetParagraphSpacingBefore(10)
        stl_paragraph.SetParagraphSpacingAfter(10)
        style_paragraph: rt.RichTextParagraphStyleDefinition = rt.RichTextParagraphStyleDefinition('paragraph')
        style_paragraph.SetStyle(stl_paragraph)
        style_paragraph.SetNextStyle('paragraph')
        self._stylesheet.AddParagraphStyle(style_paragraph)
        self.rtc.ApplyStyle(style_paragraph)
        self.rtc.SetDefaultStyle(stl_paragraph)

        # Link style
        stl_link = rt.RichTextAttr()
        stl_link.SetFlags(wx.TEXT_ATTR_URL)
        stl_link.SetFontUnderlined(True)
        stl_link.SetTextColour(wx.BLUE)
        style_link: rt.RichTextCharacterStyleDefinition = rt.RichTextCharacterStyleDefinition('url')
        style_link.SetStyle(stl_link)
        self._stylesheet.AddCharacterStyle(style_link)
        self.style_control.UpdateStyles()

    def _insert_link(self, text: str, link_id: str) -> None:
        """
        Insert a link into text at current position.
        :param text: The visible text.
        :param link_id: The ID of the link
        :return: None
        """
        self.rtc.BeginStyle(self._stylesheet.FindCharacterStyle('url').GetStyle())
        self.rtc.BeginURL(link_id)
        self.rtc.WriteText(text)
        self.rtc.EndURL()
        self.rtc.EndStyle()
        self.rtc.ApplyStyle(self._stylesheet.FindParagraphStyle('paragraph'))

    def _insert_sample_text(self) -> None:
        """
        Insert sample text.
        :return: None
        """
        self.rtc.ApplyStyle(self._stylesheet.FindParagraphStyle('paragraph'))
        self.rtc.BeginParagraphStyle('paragraph')
        self.rtc.WriteText('Paragraph adding some text ')
        self._insert_link('google', 'fe80')
        self.rtc.WriteText(' some more text after a link')
        self.rtc.Newline()
        self.rtc.EndParagraphStyle()

    def _get_style_at_pos(self, position: int = 0) -> (str, bool):
        """
        Get the style name at given position in the text. 0 - current position, -1 - before current position 1 - after
        current position.
        :param position: The position.
        :return: Style name.
        """
        style_carrier = rt.RichTextAttr()
        self.rtc.GetStyle(position, style_carrier)
        if style_carrier.GetCharacterStyleName():
            # HasUrl()
            return style_carrier.GetCharacterStyleName()
        return style_carrier.GetParagraphStyleName()

    def on_keypress(self, event: wx.CommandEvent) -> None:
        """
        Run on key up.
        :param event:
        :return:
        """
        print('\ncurrent style from ctrl: ' + self.style_control.GetValue())
        current_position = self.rtc.GetCaretPosition()
        print('previous: ' + str(self._get_style_at_pos(current_position - 1)))
        print('current pos: ' + str(current_position) + ' ' + str(self._get_style_at_pos(current_position)))
        print('next: ' + str(self._get_style_at_pos(current_position + 1)))

        event.Skip()


class MyApp(wx.App):
    """
    Main class for running the gui
    """

    def __init__(self):
        wx.App.__init__(self)
        self.frame = None

    def OnInit(self):
        self.frame = RichTextFrame(None, -1, "RichTextCtrl", size=(900, 500), style=wx.DEFAULT_FRAME_STYLE)
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True


if __name__ == "__main__":
    app = MyApp()
    app.MainLoop()

Never mind I actually managed to get it to work with RichTextStyleListBox.
It turns out that while the other style control has a usable default size and shows what it needs, this one needs to have the size set before it shows anything.
self.style_control = rt.RichTextStyleListBox(self, -1, size=(100, 160))
Then you can do this to make it show all styles in slightly smaller lines:
self.style_control.SetStyleType(0)
self.style_control.SetMargins(-5, -5)

Just in case anyone else finds this useful.