Search code examples
pythonwxpythonwxwidgets

How to read inline-styles from WxPython


I'm trying to put text into a RichTextCtrl and then, after the user has made edits, I want to get the edited text back out along with the styles. Its the second part I'm having trouble with. Out of all the methods to get styles out of the buffer, none of them are really user-friendly.

The best I've come up with is to walk through the text a character at a time with GetStyleForRange(range, style). There has got to be a better way to do this! Here's my code now, which walks through gathering a list of text segments and styles.

Please give me a better way to do this. I have to be missing something.

        buffer: wx.richtext.RichTextBuffer = self.rtc.GetBuffer()
        end = len(buffer.GetText())

        # Variables for text/style reading loop
        ch: str
        curStyle: str
        i: int = 0
        style = wx.richtext.RichTextAttr()
        text: List[str] = []
        textItems: List[Tuple[str, str]] = []

        # Read the style of the first character
        self.rtc.GetStyleForRange(wx.richtext.RichTextRange(i, i + 1), style)
        curStyle = self.describeStyle(style)

        # Loop until we hit the end. Use a while loop so we can control the index increment.
        while i < end + 1:
            # Read the current character and its style as `ch` and `newStyle`
            ch = buffer.GetTextForRange(wx.richtext.RichTextRange(i, i))
            self.rtc.GetStyleForRange(wx.richtext.RichTextRange(i, i + 1), style)
            newStyle = self.describeStyle(style)

            # If the style has changed, we flush the collected text and start new collection
            if text and newStyle != curStyle and ch != '\n':
                newText = "".join(text)
                textItems.append((newText, curStyle))
                text = []
                self.rtc.GetStyleForRange(wx.richtext.RichTextRange(i + 1, i + 2), style)
                curStyle = self.describeStyle(style)

            # Otherwise, collect the character and continue
            else:
                i += 1
                text.append(ch)

        # Capture the last text being collected
        newText = "".join(text)
        textItems.append((newText, newStyle))

Solution

  • Here's a C++ version of the solution I mentioned in the comment above. It's a simple tree walk using a queue, so I think should be translatable to python easily.

    const wxRichTextBuffer& buffer = m_richText1->GetBuffer();
    std::deque<const wxRichTextObject*> objects;
    objects.push_front(&buffer);
    
    while ( !objects.empty() )
    {
        const wxRichTextObject* curObject = objects.front();
        objects.pop_front();
    
        if ( !curObject->IsComposite() )
        {
            wxRichTextRange range = curObject->GetRange();
            const wxRichTextAttr& attr = curObject->GetAttributes();
    
            // Do something with range and attr here.
        }
        else
        {
            // This is a composite object. Add its children to the queue.
            // The children are added in reverse order to do a depth first walk.
            const wxRichTextCompositeObject* curComposite =
                static_cast<const wxRichTextCompositeObject*>(curObject);
    
            size_t childCount = curComposite->GetChildCount() ;
    
            for ( int i = childCount - 1 ; i >= 0 ; --i )
            {
                objects.push_front(curComposite->GetChild(i));
            }
        }
    }