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/