Thanks for your suggestion! It works quite well, however I do have some gripes with this solution, mostly because of stuff I left out of my original mail for the sake of brevity. So here goes:

John Finlay wrote:
I'm not sure what behavior you are looking for so I'll assume that you have one widget (e.g. Image) that you want to put inside a Viewport

Yup, only it's a custom widget, a subclass of gtk.DrawingArea.

in a ScrolledWindow.

Not necessarily, but I probably could if that works better?

I'll also assume that you want the widget to float in the center of the Window when the widget is smaller than the Window.

Mostly correct. I'd rather have top left. Or somewhere else, see below.

Finally I assume that you want the widget to be partially displayed but scrollable within the window when the widget is larger than the Window.

Correct.

So, the purpose of all this is to make a viewer for multiple sequence alignments (MSA). You can think of an MSA as a very large matrix with one amino acid (aa = a letter) per element. Both the aas and their backgrounds will be colored individually based on properties of the aa and its context in the MSA. My widget will let you interact with the MSA (draw, make selections, context menus, ...). Also there will be multiple additional widgets around this one, displaying graphs and plots for row- and column statistics in the MSA. My widget will also be used for these. When looking at this kind of stuff you need to zoom around a lot to be able to look at both the big picture and the gory details, hence the need to support both tiny and huge widgets. All these views need to be coupled so that the graphs line up with the MSA *exactly*, and for all this I want a total of 2 scrollbars.

I want to use the mouse scrolls to do most of the zooming, and when that happens I want to have the MSA "stay put" under the pointer so that the aa that was under the pointer before the zoom also is under the pointer after the zoom (not implemented in the attached example). This is easy when the widget is larger than the viewport, but I don't really know how to do that when it's smaller. If that turns out to be too hard I think the next best thing is to place small widgets topleft (how?). Or if that's also a problem I could make do with centered I suppose...

Now, my real gripe is that zooming looks "twitchy" for small widgets with the widget-in-table-in-viewport solution, and that will be painful for users to look at. I'm hoping I've done some stupid oversight somewhere (help?). Maybe that will go away if you glue the widget topleft somehow?

I attached a small example I've worked with so you can see what I mean.

Thanks for your time!
/Joel
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()
_______________________________________________
pygtk mailing list   pygtk@daa.com.au
http://www.daa.com.au/mailman/listinfo/pygtk
Read the PyGTK FAQ: http://faq.pygtk.org/

Reply via email to