The BlockView Widget

The BlockView widget is a simple container widget that handles variable-height “blocks”, such as text paragraphs, images, or other items.

This implementation uses straight-forward data structures, and is therefore limited to a relatively small number of blocks.

Here’s the core widget implementation:

 
from WCK import Widget

class BlockView(Widget):

    ui_option_width = 500
    ui_option_height = 500

    ui_option_yscrollcommand = None

    def __init__(self, master, **options):
        self.blocks = []
        self.blocks_height = self.blocks_width = 0
        self.size = 0, 0 # not known yet
        self.offset = 0 # pixel offset at top of window
        self.ui_init(master, options)
        self.update_geometry()

    def ui_handle_config(self):
        self.update_geometry()
        return int(self.ui_option_width), int(self.ui_option_height)

    def ui_handle_resize(self, width, height):
        self.size = width, height
        self.update_geometry()

    def ui_handle_clear(self, draw, x0, y0, x1, y1):
        pass

    def ui_handle_repair(self, draw, x0, y0, x1, y1):
        self.reflow_blocks(draw, self.size[0])
        top = -self.offset
        for block in self.blocks:
            bottom = top + block.height
            if bottom > y0:
                block.repair(self, draw, (0, top))
            top = bottom
            if top > y1:
                break
        else:
            # clear rest of widget
            draw.rectangle(
                (x0, bottom, x1, y1),
                self.ui_brush(self.ui_option_background)
                )

    def reflow_blocks(self, draw, width):
        # reflow blocks
        if width == self.blocks_width:
            return
        height = 0
        for block in self.blocks:
            block.setwidth(self, draw, width)
            height = height + block.height
        self.blocks_height = height
        self.blocks_width = width
        return height

    def update_geometry(self):
        # recalculate geometry related parameters, and update scrollbar
        if callable(self.ui_option_yscrollcommand):
            if self.blocks and self.size[0]:
                # calculate visible region
                height = self.reflow_blocks(self.ui_draw, self.size[0])
                start = float(self.offset) / height
                end = float(self.offset + self.size[1]) / height
                self.ui_option_yscrollcommand(start, min(end, 1.0))
            else:
                self.ui_option_yscrollcommand(0.0, 1.0)
        self.ui_damage()

    def setoffset(self, offset):
        # set topmost position
        if offset >= self.blocks_height - self.size[1]:
            offset = self.blocks_height - self.size[1]
        if offset < 0:
            offset = 0
        if offset != self.offset:
            # redraw widget
            self.offset = offset
            self.update_geometry()

    def yview(self, event, value, unit=None):
        # adjust top index
        if event == "moveto":
            self.setoffset(int(self.blocks_height * float(value) + 0.5))
        elif event == "scroll":
            # FIXME: configurable step units
            full_step = float(value) * self.size[1]
            if unit == "units":
                self.setoffset(self.offset + int(full_step * 0.1 + 0.5))
            elif unit == "pages":
                self.setoffset(self.offset + int(full_step * 0.9 + 0.5))

And here’s a minimal block implementation:

 
class Block:

    width = height = 0

    def __init__(self, color, height):
        self.color = color
        self.height = height

    def setwidth(self, widget, draw, width):
        # reflow/resize block
        self.width = width

    def repair(self, widget, draw, offset):
        # render block to screen
        x0, y0 = offset
        draw.rectangle(
            (x0, y0, x0+self.width, y0+self.height),
            widget.ui_brush(self.color), widget.ui_pen("black")
            )
from Tkinter import *

root = Tk()

view = BlockView(root)
view.pack(expand=1, fill=BOTH)

view.blocks.append(Block("red", 100))
view.blocks.append(Block("green", 200))
view.blocks.append(Block("blue", 100))

mainloop()
 

A Django site. rendered by a django application. hosted by webfaction.