import gtk

class MyDrawingarea(gtk.DrawingArea):
    __gsignals__ = {'expose-event': 'override'}
    
    def do_expose_event(self, event):
        import math
        import cairo
        alloc = self.get_allocation()
        cr = self.window.cairo_create()
        cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
        cr.clip()
        cr.set_source_rgb(1, 1, 1)
        cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
        cr.fill()
        max_radius = (alloc.width**2 + alloc.height**2) ** 0.5
        step = 10
        x, y = alloc.width/2, alloc.height/2
        for i in range(1, int(max_radius/step)):
            if i % 2:
                cr.set_source_rgb(1, 0, 0)
            else:
                cr.set_source_rgb(0, 0, 1)
            cr.arc(x, y, i * step, 0, 2 * math.pi)
            cr.stroke()
        
class BoundedScrollAdjustment(gtk.Adjustment):
    def __init__(self, value=0, lower=0, upper=0, step_incr=0, page_incr=0, page_size=0):
        super(BoundedScrollAdjustment, self).__init__(value, lower, upper, step_incr, page_incr, page_size)
    def _bound(self, value):
        return max(0, min(value, self.upper - self.page_size))
    def scroll(self, less=False, page=False):
        amount = self.page_increment if page else self.step_increment
        self.value = self._bound(self.value + amount * (-1 if less else 1))
    def update(self, page_size, upper):
        self.upper = upper
        self.page_size = page_size
        self.step_increment = 0.1 * page_size
        self.page_increment = 0.9 * page_size
        self.value = self._bound(self.value)
        
class ZoomView(gtk.Viewport):
    __gsignals__ = {'scroll-event': 'override'}
    def __init__(self):
        gtk.Viewport.__init__(self, BoundedScrollAdjustment(), BoundedScrollAdjustment())

    def do_scroll_event(self, event):
        if not self.child:
            return
        hdir = ((event.state & gtk.gdk.SHIFT_MASK) or 
                event.direction in [gtk.gdk.SCROLL_LEFT, gtk.gdk.SCROLL_RIGHT])
        less = event.direction in [gtk.gdk.SCROLL_LEFT, gtk.gdk.SCROLL_UP]
        if event.state & gtk.gdk.CONTROL_MASK:
            factor = 1.1
            if not less:
                factor **= -1
            axis = 0 if hdir else 1
            size = [i for i in self.child.get_size_request()]
            size[axis] = int(size[axis] * factor)
            self.child.set_size_request(*size)
            return
        adj = self.props.hadjustment if hdir else self.props.vadjustment
        adj.scroll(less)  
        
    def do_size_request(self, requisition):
        requisition.width, requisition.height = -1, -1
        
    def do_size_allocate(self, allocation):
        self.allocation = allocation
        child_req = self.child.get_child_requisition()
        child_alloc = gtk.gdk.Rectangle(0, 0, *child_req)
        self.child.size_allocate(child_alloc)
        self.props.hadjustment.update(allocation.width, child_alloc.width)
        self.props.vadjustment.update(allocation.height, child_alloc.height)
        if self.flags() & gtk.REALIZED:
            self.window.move_resize(*self.allocation)
        
class ZoomWindow(gtk.ScrolledWindow):
    __gsignals__ = {'scroll-event': 'override'}
    def __init__(self):
        gtk.ScrolledWindow.__init__(self, BoundedScrollAdjustment(), BoundedScrollAdjustment())

    def do_scroll_event(self, event):
        try:
            widget = self.child.child.get_children()[0]
        except Exception, e:
            return
        hdir = ((event.state & gtk.gdk.SHIFT_MASK) or 
                event.direction in [gtk.gdk.SCROLL_LEFT, gtk.gdk.SCROLL_RIGHT])
        less = event.direction in [gtk.gdk.SCROLL_LEFT, gtk.gdk.SCROLL_UP]
        if event.state & gtk.gdk.CONTROL_MASK:
            factor = 1.1
            if not less:
                factor **= -1
            axis = 0 if hdir else 1
            size = [i for i in widget.get_size_request()]
            size[axis] = int(size[axis] * factor)
            widget.set_size_request(*size)
            return
        adj = self.props.hadjustment if hdir else self.props.vadjustment
        adj.scroll(less)  

def make_my_first_attempt_window(width, height):
    d = MyDrawingarea()
    d.set_size_request(width, height)
    v = ZoomView()
    v.add(d)
    v.set_size_request(10, 10)
    t = gtk.Table(2, 2)
    t.attach(gtk.HScrollbar(v.props.hadjustment), 0, 1, 1, 2, yoptions=0)
    t.attach(gtk.VScrollbar(v.props.vadjustment), 1, 2, 0, 1, xoptions=0)
    t.attach(v, 0, 1, 0, 1)
    w = gtk.Window()
    w.add(t)
    w.set_size_request(200, 200)
    w.connect('destroy', gtk.main_quit)
    w.set_title('my first attempt %sx%s' % (width, height))
    w.show_all()

def make_johns_suggestion_window():
    d = MyDrawingarea()
    d.set_size_request(100, 100)
    t1 = gtk.Table(1, 1)
    t1.attach(d, 0, 1, 0, 1, xoptions=gtk.EXPAND, yoptions=gtk.EXPAND)
    z = ZoomWindow()
    z.add_with_viewport(t1)
    w = gtk.Window()
    w.add(z)
    w.set_size_request(200, 200)
    w.connect('destroy', gtk.main_quit)
    w.set_title('pygtk list suggestion')
    w.show_all()
    
make_my_first_attempt_window(100, 100)
make_my_first_attempt_window(1000, 1000)
make_johns_suggestion_window()
gtk.main()

