Comments On ToasterBox

All that does is force all lines to be at most a particular width, it
doesn't guarantee that each line will have as many words on it as
possible, which is the general point of using textwrap.

Add the following to a new textwrap-like module, and use it to wrap your
text instead. I include the len() function replacement, as well as the
two methods which need to use this new len() function. Note that you
must include a proper DC instance as a global in this module, or via
some other means.

- Josiah

import textwrap

def len(string):
    return DC.GetTextExtent(string)[0]

class VariableTextWrapper(textwrap.TextWrapper):
    def _handle_long_word(self, chunks, cur_line, cur_len, width):
        """_handle_long_word(chunks : [string],
                             cur_line : [string],
                             cur_len : int, width : int)

        Handle a chunk of text (most likely a word, not whitespace) that
        is too long to fit in any line.
        """
        space_left = max(width - cur_len, 1)

        # If we're allowed to break long words, then do so: put as much
        # of the next chunk onto the current line as will fit.
        if self.break_long_words:
            use = 0
            x = len(chunks[0][use])
            while x < space_left:
                space_left -= x
                use += 1
                x = len(chunks[0][use])
            
            cur_line.append(chunks[0][:use])
            chunks[0] = chunks[0][use:]

        # Otherwise, we have to preserve the long word intact. Only add
        # it to the current line if there's nothing already there --
        # that minimizes how much we violate the width constraint.
        elif not cur_line:
            cur_line.append(chunks.pop(0))

        # If we're not allowed to break long words, and there's already
        # text on the current line, do nothing. Next time through the
        # main loop of _wrap_chunks(), we'll wind up here again, but
        # cur_len will be zero, so the next line will be entirely
        # devoted to the long word that we can't handle right now.

    def _wrap_chunks(self, chunks):
        """_wrap_chunks(chunks : [string]) -> [string]

        Wrap a sequence of text chunks and return a list of lines of
        length 'self.width' or less. (If 'break_long_words' is false,
        some lines may be longer than this.) Chunks correspond roughly
        to words and the whitespace between them: each chunk is
        indivisible (modulo 'break_long_words'), but a line break can
        come between any two chunks. Chunks should not have internal
        whitespace; ie. a chunk is either all whitespace or a "word".
        Whitespace chunks will be removed from the beginning and end of
        lines, but apart from that whitespace is preserved.
        """
        lines =
        if self.width <= 0:
            raise ValueError("invalid width %r (must be > 0)" % self.width)

        while chunks:

            # Start the list of chunks that will make up the current line.
            # cur_len is just the length of all the chunks in cur_line.
            cur_line =
            cur_len = 0

            # Figure out which static string will prefix this line.
            if lines:
                indent = self.subsequent_indent
            else:
                indent = self.initial_indent

            # Maximum width for this line.
            width = self.width - len(indent)

            # First chunk on line is whitespace -- drop it, unless this
            # is the very beginning of the text (ie. no lines started yet).
            if chunks[0].strip() == '' and lines:
                del chunks[0]

            while chunks:
                l = len(chunks[0])

                # Can at least squeeze this chunk onto the current line.
                if cur_len + l <= width:
                    cur_line.append(chunks.pop(0))
                    cur_len += l

                # Nope, this line is full.
                else:
                    break

            # The current line is full, and the next chunk is too big to
            # fit on *any* line (not just this one).
            if chunks and len(chunks[0]) > width:
                self._handle_long_word(chunks, cur_line, cur_len, width)

            # If the last chunk on this line is all whitespace, drop it.
            if cur_line and cur_line[-1].strip() == '':
                del cur_line[-1]

            # Convert current line back to a string and store it in list
            # of all lines (return value).
            if cur_line:
                lines.append(indent + ''.join(cur_line))

        return lines

ยทยทยท

"E. A. Tacao" <e.a.tacao@estadao.com.br> wrote:

Saturday, November 5, 2005, 9:24:08 AM, Michael Moriarity wrote:

> Looking at the documentation for this module shows that it is a
> character counting text wrapper, thus it will only work with a fixed
> width font. Lines of text in a proportional font will be different
> lengths when rendered, determined by the specific characters on each
> line.

> To get proper wrapping behaviour with proportional fonts, I believe
> it is necessary to do something like adding one word at a time to a
> line, and using wxDC.GetTextExtent() on the resulting line to test
> if it is too long.

You are right, it's necessary to test using GetTextExtent(), but
textwrap can be used to help on this (and so save the joins, splits
and appends). The lines below show how it can be done for ToasterBox:

        ...
        max_len = len(pText)
        tw, th = popup_size = self._parent2._popupsize

        while 1:
            lines = textwrap.wrap(pText, max_len)

            for line in lines:
                w, h = dc.GetTextExtent(line)
                if w > tw - border * 2:
                    max_len -= 1
                    break
            else:
                break
        ...