Exemple #1
0
 def _load_scrollbar(self, height):
     if self._content.content_height > height:
         if self._scrollbar is None:
             self._scrollbar = VScrollbar(self.max_height)
             self._scrollbar.set_manager(self._manager)
             self._scrollbar.parent = self
             self._scrollbar.load()
             self._scrollbar.set_knob_size(self.height, self._content.content_height)
     # if smaller, we unload it if it is loaded
     elif self._scrollbar is not None:
         self._scrollbar.unload()
         self._scrollbar = None
Exemple #2
0
 def _load_scrollbar(self, height):
     if self._content.content_height > height:
         if self._scrollbar is None:
             self._scrollbar = VScrollbar(self.max_height)
             self._scrollbar.set_manager(self._manager)
             self._scrollbar.parent = self
             self._scrollbar.load()
             self._scrollbar.set_knob_size(self.height, self._content.content_height)
     # if smaller, we unload it if it is loaded
     elif self._scrollbar is not None:
         self._scrollbar.unload()
         self._scrollbar = None
    def _load_scrollbars(self, width, height):
        # if content size bigger than our size,
        # we load a scrollbar
        if self.content.width > width:
            if self._hscrollbar is None:
                self._hscrollbar = HScrollbar(width)
                self._hscrollbar.set_manager(self._manager)
                self._hscrollbar.parent = self
                self._hscrollbar.load()
        # if smaller, we unload it if it is loaded
        elif self._hscrollbar is not None:
            self._hscrollbar.delete()
            self._hscrollbar = None

        if self.content.height > height:
            if self._vscrollbar is None:
                self._vscrollbar = VScrollbar(height)
                self._vscrollbar.set_manager(self._manager)
                self._vscrollbar.parent = self
                self._vscrollbar.load()
        elif self._vscrollbar is not None:
            self._vscrollbar.delete()
            self._vscrollbar = None
            self._manager.set_wheel_target(None)
    def _load_scrollbars(self, width, height):
        # if content size bigger than our size,
        # we load a scrollbar
        if self.content.width > width:
            if self._hscrollbar is None:
                self._hscrollbar = HScrollbar(width)
                self._hscrollbar.set_manager(self._manager)
                self._hscrollbar.parent = self
                self._hscrollbar.load()
        # if smaller, we unload it if it is loaded
        elif self._hscrollbar is not None:
            self._hscrollbar.delete()
            self._hscrollbar = None

        if self.content.height > height:
            if self._vscrollbar is None:
                self._vscrollbar = VScrollbar(height)
                self._vscrollbar.set_manager(self._manager)
                self._vscrollbar.parent = self
                self._vscrollbar.load()
        elif self._vscrollbar is not None:
            self._vscrollbar.delete()
            self._vscrollbar = None
            self._manager.set_wheel_target(None)
Exemple #5
0
class Document(Controller, Viewer):
    """
    Allows you to embed a document within the GUI, which includes a
    vertical scrollbar.
    """
    def __init__(self, document, width=0, height=0, is_fixed_size=False):
        Viewer.__init__(self, width, height)
        Controller.__init__(self)

        self.max_height = height
        if isinstance(document, str):
            self._document = pyglet.text.document.UnformattedDocument(document)
        else:
            self._document = document
        self._content = None
        self.content_width = width
        self._scrollbar = None
        self.set_document_style = False
        self.is_fixed_size = is_fixed_size

    def hit_test(self, x, y):
        if self._content is not None:
            return Rectangle(self._content.x,
                             self._content.y,
                             self._content.width,
                             self._content.height).is_inside(x, y)
        else:
            return False

    def _load_scrollbar(self, height):
        if self._content.content_height > height:
            if self._scrollbar is None:
                self._scrollbar = VScrollbar(self.max_height)
                self._scrollbar.set_manager(self._manager)
                self._scrollbar.parent = self
                self._scrollbar.load()
                self._scrollbar.set_knob_size(self.height, self._content.content_height)
        # if smaller, we unload it if it is loaded
        elif self._scrollbar is not None:
            self._scrollbar.unload()
            self._scrollbar = None

    def load_graphics(self):
        if not self.set_document_style:
            self.do_set_document_style(self._manager)

        self._content = pyglet.text.layout.IncrementalTextLayout(self._document,
                                                                 self.content_width, self.max_height,
                                                                 multiline=True, **self.get_batch('background'))

    def unload_graphics(self):
        self._content.delete()
        if self._scrollbar is not None:
            self._scrollbar.unload()
            self._scrollbar = None

    def do_set_document_style(self, dialog):
        self.set_document_style = True

        # Check the style runs to make sure we don't stamp on anything
        # set by the user
        self._do_set_document_style('color', dialog.theme['text_color'])
        self._do_set_document_style('font_name', dialog.theme['font'])
        self._do_set_document_style('font_size', dialog.theme['font_size'])

    def _do_set_document_style(self, attr, value):
        length = len(self._document.text)
        runs = [(start, end, doc_value) for start, end, doc_value in
                self._document.get_style_runs(attr).ranges(0, length)
                if doc_value is not None]
        if not runs:
            terminator = len(self._document.text)
        else:
            terminator = runs[0][0]
        self._document.set_style(0, terminator, {attr: value})

    def get_text(self):
        return self._document.text

    def layout(self):
        if self._scrollbar is not None:
            self._scrollbar.set_position(self.x + self._content.content_width, self.y)
            pos = self._scrollbar.get_knob_pos()
            if pos != -self._content.view_y:
                self._content.view_y = -pos

        self._content.begin_update()
        self._content.x = self.x
        self._content.y = self.y
        self._content.end_update()

        if self._scrollbar is not None:
            self._scrollbar.set_position(self.x + self.content_width, self.y)

    def on_gain_highlight(self):
        if self._scrollbar is not None:
            self._manager.set_wheel_target(self._scrollbar)

    def on_lose_highlight(self):
        self._manager.set_wheel_target(None)

    def compute_size(self):
        if self.is_fixed_size or (self.max_height and self._content.content_height > self.max_height):
            height = self.max_height
        else:
            height = self._content.content_height
        self._content.height = height

        self._load_scrollbar(height)
        if self._scrollbar is not None:
            self._scrollbar.set_knob_size(height, self._content.content_height)
            self._scrollbar.compute_size()
            width = self.content_width + self._scrollbar.width
        else:
            width = self.content_width

        return width, height

    def set_text(self, text):
        self._document.text = text
        self.layout()

    def delete(self):
        Controller.delete(self)
        Viewer.delete(self)
class Scrollable(Wrapper, Controller, ControllerManager):
    def __init__(self, content=None, width=None, height=None, is_fixed_size=False):
        if is_fixed_size:
            assert width is not None and height is not None
        Wrapper.__init__(self, content)
        ControllerManager.__init__(self)
        self.max_width = width
        self.max_height = height
        self.is_fixed_size = is_fixed_size

        self._hscrollbar = None
        self._vscrollbar = None
        self._content_width = 0
        self._content_height = 0
        self._content_x = 0
        self._content_y = 0

        # We emulate some aspects of Manager here.
        self._theme = None
        self.batch = None
        self.root_group = None
        self.group = {'panel': None, 'background': None, 'foreground': None, 'highlight': None}

    @Managed.theme.getter
    def theme(self):
        return self._theme

    def set_manager(self, manager):
        Controller.set_manager(self, manager)
        self._theme = manager.theme
        self.batch = manager.batch
        self.root_group = ScrollableGroup(0, 0, self.width, self.height, parent=manager.group['foreground'])
        self.group['panel'] = pyglet.graphics.OrderedGroup(0, self.root_group)
        self.group['background'] = pyglet.graphics.OrderedGroup(10, self.root_group)
        self.group['foreground'] = pyglet.graphics.OrderedGroup(20, self.root_group)
        self.group['highlight'] = pyglet.graphics.OrderedGroup(30, self.root_group)
        self.content.set_manager(self)
        self.content.parent = self

    def unload_graphics(self):
        Wrapper.unload_graphics(self)
        if self._hscrollbar is not None:
            self._hscrollbar.unload()
            self._hscrollbar = None
        if self._vscrollbar is not None:
            self._vscrollbar.unload()
            self._vscrollbar = None

    def expand(self, width, height):
        if self.content.is_expandable():
            if self._vscrollbar is not None:
                self._content_width = width
            else:
                self._content_width = width
            if self._hscrollbar is not None:
                self._content_height = height
            else:
                self._content_height = height
            self.content.expand(max(self._content_width, self.content.width),
                                max(self._content_height, self.content.height))
        self.width, self.height = width, height

    def hit_test(self, x, y):
        # We only intercept events for the content region, not for
        # the scrollbars. They can handle themselves.
        return y >= self._content_y < self._content_y + self._content_height and \
               self._content_x <= x < self._content_x + self._content_width

    def is_expandable(self):
        return True

    def _load_scrollbars(self, width, height):
        # if content size bigger than our size,
        # we load a scrollbar
        if self.content.width > width:
            if self._hscrollbar is None:
                self._hscrollbar = HScrollbar(width)
                self._hscrollbar.set_manager(self._manager)
                self._hscrollbar.parent = self
                self._hscrollbar.load()
        # if smaller, we unload it if it is loaded
        elif self._hscrollbar is not None:
            self._hscrollbar.delete()
            self._hscrollbar = None

        if self.content.height > height:
            if self._vscrollbar is None:
                self._vscrollbar = VScrollbar(height)
                self._vscrollbar.set_manager(self._manager)
                self._vscrollbar.parent = self
                self._vscrollbar.load()
        elif self._vscrollbar is not None:
            self._vscrollbar.delete()
            self._vscrollbar = None
            self._manager.set_wheel_target(None)

    def layout(self):
        Wrapper.layout(self)
        # Work out the adjusted content width and height
        y = self.y
        if self._hscrollbar is not None:
            self._hscrollbar.set_position(self.x, self.y)
            y += self._hscrollbar.height
        if self._vscrollbar is not None:
            self._vscrollbar.set_position(self.x + self._content_width, y)

        # Set the scissor group
        self.root_group.x, self.root_group.y = self.x - 1, y - 1
        self.root_group.width = self._content_width + 1
        self.root_group.height = self._content_height + 1

        # Work out the content layout
        self._content_x, self._content_y = self.x, y
        left = self.x
        top = y + self._content_height - self.content.height
        if self._hscrollbar:
            left -= self._hscrollbar.get_knob_pos()
        if self._vscrollbar:
            top += self._vscrollbar.get_knob_pos()

        self.content.set_position(left, top)

    def on_gain_highlight(self):
        if self._hscrollbar is not None:
            self._manager.set_wheel_hint(self._hscrollbar)
        if self._vscrollbar is not None:
            self._manager.set_wheel_target(self._vscrollbar)

    def on_lose_highlight(self):
        self._manager.set_wheel_target(None)
        self._manager.set_wheel_hint(None)

    def compute_size(self):
        content_width, content_height = self.content.compute_size()

        width = min(self.max_width or content_width, content_width)
        height = min(self.max_height or content_height, content_height)

        self._content_width = width
        self._content_height = height

        # we have to load the scrolls here because they require knowing content size
        self._load_scrollbars(width, height)
        if self._hscrollbar is not None:
            self._hscrollbar.set_knob_size(self._content_width, content_height)
            _, scroll_height = self._hscrollbar.compute_size()

            height += scroll_height
        if self._vscrollbar is not None:
            self._vscrollbar.set_knob_size(self._content_height, content_height)
            scroll_width, _ = self._vscrollbar.compute_size()

            width += scroll_width
        return width, height

    def reset_size(self, reset_parent=True):
        super(Scrollable, self).reset_size(reset_parent)
        if self._hscrollbar is not None:
            self._hscrollbar.reset_size(reset_parent=False)
        if self._vscrollbar is not None:
            self._vscrollbar.reset_size(reset_parent=False)

    def delete(self):
        Wrapper.delete(self)
        ControllerManager.delete(self)
Exemple #7
0
class Document(Controller, Viewer):
    """
    Allows you to embed a document within the GUI, which includes a
    vertical scrollbar.
    """
    def __init__(self,
                 document,
                 width=0,
                 height=0,
                 is_fixed_size=False,
                 background=False,
                 font_size=13,
                 font_name='Segoe UI',
                 font_color=None,
                 chat=False):
        Viewer.__init__(self, width, height)
        Controller.__init__(self)

        self.max_height = height
        if isinstance(document, str):
            self._document = pyglet.text.document.UnformattedDocument(document)
        else:
            self._document = document
        self.h1 = height
        self.w1 = width
        self.chat = chat
        self.fontSize = font_size
        self.fontName = font_name
        self.fontColor = font_color
        self._bgcolor = background
        self._bg = None
        self._content = None
        self.content_width = width
        self._scrollbar = None
        self.set_document_style = False
        self.firstTimeLoad = True
        self.is_fixed_size = is_fixed_size

    def hit_test(self, x, y):
        if self._content is not None:
            return Rectangle(self._content.x, self._content.y,
                             self._content.width,
                             self._content.height).is_inside(x, y)
        else:
            return False

    def _load_scrollbar(self, height):
        if self._content.content_height > height:
            if self._scrollbar is None:
                self._scrollbar = VScrollbar(self.max_height)
                self._scrollbar.set_manager(self._manager)
                self._scrollbar.parent = self
                self._scrollbar.load()
                self._scrollbar.set_knob_size(self.height,
                                              self._content.content_height)
        # if smaller, we unload it if it is loaded
        elif self._scrollbar is not None:
            self._scrollbar.unload()
            self._scrollbar = None

    def load_graphics(self):
        if self._bgcolor is True:
            self._bg = self.theme[['document']]['image'].generate(
                self.theme[['document']]['gui_color'],
                **self.get_batch("background"))

        if not self.set_document_style and not self.chat:
            self.do_set_document_style(self._manager)
        self._content = pyglet.text.layout.IncrementalTextLayout(
            self._document,
            self.content_width,
            self.max_height,
            multiline=True,
            **self.get_batch('foreground'))

    def unload_graphics(self):
        if self._bg is not None:
            self._bg.unload()
        self._content.delete()
        if self._scrollbar is not None:
            self._scrollbar.unload()
            self._scrollbar = None

    def do_set_document_style(self, dialog):
        self.set_document_style = True
        # Check the style runs to make sure we don't stamp on anything
        # set by the user
        self._do_set_document_style('color', dialog.theme['text_color'])
        self._do_set_document_style('font_name', dialog.theme['font'])
        self._do_set_document_style('font_size', dialog.theme['font_size'])

    def _do_set_document_style(self, attr, value):
        length = len(self._document.text)
        runs = [(start, end, doc_value) for start, end, doc_value in
                self._document.get_style_runs(attr).ranges(0, length)
                if doc_value is not None]
        if not runs:
            terminator = len(self._document.text)
        else:
            terminator = runs[0][0]
        self._document.set_style(0, terminator, {attr: value})

    def get_text(self):
        return self._document.text

    def layout(self):
        if self._bgcolor is True:
            self._bg.update(self.x, self.y, self.w1 + 2, self.h1)
        if self._scrollbar is not None:
            self._scrollbar.set_position(
                self.x + self._content.content_width + 2, self.y)
            pos = self._scrollbar.get_knob_pos()
            if pos != -self._content.view_y:
                self._content.view_y = -pos

        self._content.begin_update()
        self._content.x = self.x + 2
        self._content.y = self.y
        self._content.end_update()
        if self._scrollbar is not None:
            self._scrollbar.set_position(self.x + self.content_width + 2,
                                         self.y)

    def on_gain_highlight(self):
        if self._scrollbar is not None:
            self._manager.set_wheel_target(self._scrollbar)

    def on_lose_highlight(self):
        self._manager.set_wheel_target(None)

    def compute_size(self):
        if self.is_fixed_size or (
                self.max_height
                and self._content.content_height > self.max_height):
            height = self.max_height
        else:
            height = self._content.content_height
        self._content.height = height
        self._load_scrollbar(height)
        if self._scrollbar is not None:

            self._scrollbar.set_knob_size(height, self._content.content_height)
            if self.firstTimeLoad:
                self._scrollbar.set_knob_pos(0)
                self.firstTimeLoad = False
            else:
                self._scrollbar.set_knob_pos(1)
            self._scrollbar.compute_size()

            width = self.content_width + self._scrollbar.width
            #print self._scrollbar.get_knob_pos()
        else:
            width = self.content_width
        return width, height

    def set_text(self, text):
        self._document.text = text
        self.compute_size()
        self.layout()

    def update_text(self, text):
        realtext = '{font_size ' + str(
            self.fontSize
        ) + '}{font_name "' + self.fontName + '"}{wrap "char"}' + text
        t1 = time.time() * 1000
        #self._document = pyglet.text.decode_attributed(realtext)

        #self._document.insert_text(0,text)
        #print len(self._document._text)
        #print self._document.__dict__
        t2 = time.time() * 1000
        #self._content.begin_update()
        #self._content._set_document(self._document)
        t3 = time.time() * 1000
        #self._content.end_update()
        #self.compute_size()
        t4 = time.time() * 1000
        #self.layout()
        t5 = time.time() * 1000
        print "Total: " + str(int(t5 - t1)) + " ms. t2-t1: " + str(
            int(t2 - t1)) + " and t3-t2: " + str(
                int(t3 - t2)) + " and t4-t3: " + str(
                    int(t4 - t3)) + " and t5-t4: " + str(int(t5 - t4))

    def delete(self):
        Controller.delete(self)
        Viewer.delete(self)
class Scrollable(Wrapper, Controller, ControllerManager):
    def __init__(self,
                 content=None,
                 width=None,
                 height=None,
                 is_fixed_size=False):
        if is_fixed_size:
            assert width is not None and height is not None
        Wrapper.__init__(self, content)
        ControllerManager.__init__(self)
        self.max_width = width
        self.max_height = height
        self.is_fixed_size = is_fixed_size

        self._hscrollbar = None
        self._vscrollbar = None
        self._content_width = 0
        self._content_height = 0
        self._content_x = 0
        self._content_y = 0

        # We emulate some aspects of Manager here.
        self._theme = None
        self.batch = None
        self.root_group = None
        self.group = {
            'panel': None,
            'background': None,
            'foreground': None,
            'highlight': None
        }

    @Managed.theme.getter
    def theme(self):
        return self._theme

    def set_manager(self, manager):
        Controller.set_manager(self, manager)
        self._theme = manager.theme
        self.batch = manager.batch
        self.root_group = ScrollableGroup(0,
                                          0,
                                          self.width,
                                          self.height,
                                          parent=manager.group['foreground'])
        self.group['panel'] = pyglet.graphics.OrderedGroup(0, self.root_group)
        self.group['background'] = pyglet.graphics.OrderedGroup(
            10, self.root_group)
        self.group['foreground'] = pyglet.graphics.OrderedGroup(
            20, self.root_group)
        self.group['highlight'] = pyglet.graphics.OrderedGroup(
            30, self.root_group)
        self.content.set_manager(self)
        self.content.parent = self

    def unload_graphics(self):
        Wrapper.unload_graphics(self)
        if self._hscrollbar is not None:
            self._hscrollbar.unload()
            self._hscrollbar = None
        if self._vscrollbar is not None:
            self._vscrollbar.unload()
            self._vscrollbar = None

    def expand(self, width, height):
        if self.content.is_expandable():
            if self._vscrollbar is not None:
                self._content_width = width
            else:
                self._content_width = width
            if self._hscrollbar is not None:
                self._content_height = height
            else:
                self._content_height = height
            self.content.expand(max(self._content_width, self.content.width),
                                max(self._content_height, self.content.height))
        self.width, self.height = width, height

    def hit_test(self, x, y):
        # We only intercept events for the content region, not for
        # the scrollbars. They can handle themselves.
        return y >= self._content_y < self._content_y + self._content_height and \
               self._content_x <= x < self._content_x + self._content_width

    def is_expandable(self):
        return True

    def _load_scrollbars(self, width, height):
        # if content size bigger than our size,
        # we load a scrollbar
        if self.content.width > width:
            if self._hscrollbar is None:
                self._hscrollbar = HScrollbar(width)
                self._hscrollbar.set_manager(self._manager)
                self._hscrollbar.parent = self
                self._hscrollbar.load()
        # if smaller, we unload it if it is loaded
        elif self._hscrollbar is not None:
            self._hscrollbar.delete()
            self._hscrollbar = None

        if self.content.height > height:
            if self._vscrollbar is None:
                self._vscrollbar = VScrollbar(height)
                self._vscrollbar.set_manager(self._manager)
                self._vscrollbar.parent = self
                self._vscrollbar.load()
        elif self._vscrollbar is not None:
            self._vscrollbar.delete()
            self._vscrollbar = None
            self._manager.set_wheel_target(None)

    def layout(self):
        Wrapper.layout(self)
        # Work out the adjusted content width and height
        y = self.y
        if self._hscrollbar is not None:
            self._hscrollbar.set_position(self.x, self.y)
            y += self._hscrollbar.height
        if self._vscrollbar is not None:
            self._vscrollbar.set_position(self.x + self._content_width, y)

        # Set the scissor group
        self.root_group.x, self.root_group.y = self.x - 1, y - 1
        self.root_group.width = self._content_width + 1
        self.root_group.height = self._content_height + 1

        # Work out the content layout
        self._content_x, self._content_y = self.x, y
        left = self.x
        top = y + self._content_height - self.content.height
        if self._hscrollbar:
            left -= self._hscrollbar.get_knob_pos()
        if self._vscrollbar:
            top += self._vscrollbar.get_knob_pos()

        self.content.set_position(left, top)

    def on_gain_highlight(self):
        if self._hscrollbar is not None:
            self._manager.set_wheel_hint(self._hscrollbar)
        if self._vscrollbar is not None:
            self._manager.set_wheel_target(self._vscrollbar)

    def on_lose_highlight(self):
        self._manager.set_wheel_target(None)
        self._manager.set_wheel_hint(None)

    def compute_size(self):
        content_width, content_height = self.content.compute_size()

        width = min(self.max_width or content_width, content_width)
        height = min(self.max_height or content_height, content_height)

        self._content_width = width
        self._content_height = height

        # we have to load the scrolls here because they require knowing content size
        self._load_scrollbars(width, height)
        if self._hscrollbar is not None:
            self._hscrollbar.set_knob_size(self._content_width, content_height)
            _, scroll_height = self._hscrollbar.compute_size()

            height += scroll_height
        if self._vscrollbar is not None:
            self._vscrollbar.set_knob_size(self._content_height,
                                           content_height)
            scroll_width, _ = self._vscrollbar.compute_size()

            width += scroll_width
        return width, height

    def reset_size(self, reset_parent=True):
        super(Scrollable, self).reset_size(reset_parent)
        if self._hscrollbar is not None:
            self._hscrollbar.reset_size(reset_parent=False)
        if self._vscrollbar is not None:
            self._vscrollbar.reset_size(reset_parent=False)

    def delete(self):
        Wrapper.delete(self)
        ControllerManager.delete(self)
Exemple #9
0
class Document(Controller, Viewer):
    """
    Allows you to embed a document within the GUI, which includes a
    vertical scrollbar.
    """
    def __init__(self, document, width=0, height=0, is_fixed_size=False):
        Viewer.__init__(self, width, height)
        Controller.__init__(self)

        self.max_height = height
        if isinstance(document, str):
            self._document = pyglet.text.document.UnformattedDocument(document)
        else:
            self._document = document
        self._content = None
        self.content_width = width
        self._scrollbar = None
        self.set_document_style = False
        self.is_fixed_size = is_fixed_size

    def hit_test(self, x, y):
        if self._content is not None:
            return Rectangle(self._content.x,
                             self._content.y,
                             self._content.width,
                             self._content.height).is_inside(x, y)
        else:
            return False

    def _load_scrollbar(self, height):
        if self._content.content_height > height:
            if self._scrollbar is None:
                self._scrollbar = VScrollbar(self.max_height)
                self._scrollbar.set_manager(self._manager)
                self._scrollbar.parent = self
                self._scrollbar.load()
                self._scrollbar.set_knob_size(self.height, self._content.content_height)
        # if smaller, we unload it if it is loaded
        elif self._scrollbar is not None:
            self._scrollbar.unload()
            self._scrollbar = None

    def load_graphics(self):
        if not self.set_document_style:
            self.do_set_document_style(self._manager)

        self._content = pyglet.text.layout.IncrementalTextLayout(self._document,
                                                                 self.content_width, self.max_height,
                                                                 multiline=True, **self.get_batch('background'))

    def unload_graphics(self):
        self._content.delete()
        if self._scrollbar is not None:
            self._scrollbar.unload()
            self._scrollbar = None

    def do_set_document_style(self, dialog):
        self.set_document_style = True

        # Check the style runs to make sure we don't stamp on anything
        # set by the user
        self._do_set_document_style('color', dialog.theme['text_color'])
        self._do_set_document_style('font_name', dialog.theme['font'])
        self._do_set_document_style('font_size', dialog.theme['font_size'])

    def _do_set_document_style(self, attr, value):
        length = len(self._document.text)
        runs = [(start, end, doc_value) for start, end, doc_value in
                self._document.get_style_runs(attr).ranges(0, length)
                if doc_value is not None]
        if not runs:
            terminator = len(self._document.text)
        else:
            terminator = runs[0][0]
        self._document.set_style(0, terminator, {attr: value})

    def get_text(self):
        return self._document.text

    def layout(self):
        if self._scrollbar is not None:
            self._scrollbar.set_position(self.x + self._content.content_width, self.y)
            pos = self._scrollbar.get_knob_pos()
            if pos != -self._content.view_y:
                self._content.view_y = -pos

        self._content.begin_update()
        self._content.x = self.x
        self._content.y = self.y
        self._content.end_update()

        if self._scrollbar is not None:
            self._scrollbar.set_position(self.x + self.content_width, self.y)

    def on_gain_highlight(self):
        if self._scrollbar is not None:
            self._manager.set_wheel_target(self._scrollbar)

    def on_lose_highlight(self):
        self._manager.set_wheel_target(None)

    def compute_size(self):
        if self.is_fixed_size or (self.max_height and self._content.content_height > self.max_height):
            height = self.max_height
        else:
            height = self._content.content_height
        self._content.height = height

        self._load_scrollbar(height)
        if self._scrollbar is not None:
            self._scrollbar.set_knob_size(height, self._content.content_height)
            self._scrollbar.compute_size()
            width = self.content_width + self._scrollbar.width
        else:
            width = self.content_width

        return width, height

    def set_text(self, text):
        self._document.text = text
        self.compute_size()
        self.layout()

    def delete(self):
        Controller.delete(self)
        Viewer.delete(self)