Reputation: 365
I have a problem updating a custom GTK+ widget: VUWidget. A Generator class is updating the level property of the class Section, whose subclass has a VUWidget property. The Generator class updates the values of the level properties correctly.
import pygtk
pygtk.require("2.0")
import gtk, gtk.gdk
import cairo
#=========================================================================
class VUWidget(gtk.DrawingArea):
__gtype_name__ = 'VUWidget'
_BACKGROUND_RGB = (0., 0., 0.)
_LEVEL_GRADIENT_BOTTOM_ORGBA = (1, 0, 1, 0, 1)
_LEVEL_GRADIENT_TOP_ORGBA = (0, 1, 0, 0, 1)
#_____________________________________________________________________
def __init__(self, section):
gtk.DrawingArea.__init__(self)
self.section = section
self.connect("configure_event", self.on_configure_event)
self.connect("expose-event", self.OnDraw)
self.section.connect("changed-value", self.ValueChanged)
self.set_size_request(30,100)
self.realize()
self.show()
#_____________________________________________________________________
def ValueChanged(self, widget, level):
#print ("Callback %f" % self.section.GetLevel())
rect = self.get_allocation()
self.window.invalidate_rect(rect, False)
return False
#_____________________________________________________________________
def GenerateBackground(self):
rect = self.get_allocation()
ctx = cairo.Context(self.source)
ctx.set_line_width(2)
ctx.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
pat = cairo.LinearGradient(0.0, 0.0, 0, rect.height)
pat.add_color_stop_rgba(*self._LEVEL_GRADIENT_BOTTOM_ORGBA)
pat.add_color_stop_rgba(*self._LEVEL_GRADIENT_TOP_ORGBA)
ctx.rectangle(0, 0, rect.width, rect.height)
ctx.set_source(pat)
ctx.fill()
#_____________________________________________________________________
def on_configure_event(self, widget, event):
self.source = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.allocation.width, self.allocation.height)
self.GenerateBackground()
return self.OnDraw(widget, event)
#_____________________________________________________________________
def OnDraw(self, widget, event):
ctx = self.window.cairo_create()
ctx.save()
rect = self.get_allocation()
ctx.rectangle(0, 0, rect.width, rect.height)
ctx.set_source_rgb(*self._BACKGROUND_RGB)
ctx.fill()
ctx.rectangle(0, rect.height * (1. - self.section.GetLevel()), rect.width, rect.height)
ctx.clip()
ctx.set_source_surface(self.source, 0, 0)
ctx.paint()
ctx.restore()
return False
#_____________________________________________________________________
def Destroy(self):
del self.source
self.destroy()
#_____________________________________________________________________
#=========================================================================
the signal is implemented in the class Section and is emitted correctly
__gsignals__ = {
'changed-value' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_FLOAT,))
}
Regards Ck
Upvotes: 1
Views: 1190
Reputation: 4367
Here's one way to do it, I subclass this and provide a custom draw method. If there are any changes to the state, I call invalidate, forcing the widget to re-draw through the expose-event. If you need a different context, you can provide it in the on_expose_event method.
So basically, do all your drawing in one place only. If the widget should display something different, set the new state and re-render. Makes it easy to maintain.
#cairovehicle.py
import gtk
class CairoVehicle(gtk.DrawingArea):
def __init__(self):
gtk.DrawingArea.__init__(self)
self.connect("expose-event", self.on_expose_event)
def get_background(self):
"""Serves as a caching solution."""
return self.__bg
def set_background(self, pattern):
"""Serves as a caching solution."""
self.__bg = pattern
def get_brush(self):
"""Serves as a caching solution."""
return self.__brush
def set_brush(self, pattern):
"""Serves as a caching solution."""
self.__brush = pattern
def on_expose_event(self, widget, event):
context = self.window.cairo_create()
# Restrict cairo to the exposed area
context.rectangle(*event.area)
context.clip()
self.width, self.height = self.window.get_size()
self.draw(context)
def on_configure_event(self, widget, event):
"""Override this in case you want to do something when
the widget's size changes."""
return super(CairoVehicle, self).on_configure_event(widget, event)
def invalidate(self):
"""Force a re-rendering of the window."""
rect = self.get_allocation()
# Compensate for parent offset, if any.
parent = self.get_parent()
if parent:
offset = parent.get_allocation()
rect.x -= offset.x
rect.y -= offset.y
self.window.invalidate_rect(rect, False)
def draw(self, context):
"""Override this."""
# do custom drawing here
raise NotImplementedError()
def make_grid(self, context, fgcolor, bgcolor, gapwidth, linewidth,
width, height):
context.set_source_rgba(*bgcolor)
context.rectangle(0, 0, width, height)
context.fill()
context.set_source_rgba(*fgcolor)
context.set_line_width(linewidth)
# uneven linewidths lead to blurry displaying when set on integer
# coordinates, so in that case move coordinates away by half a
# pixel.
adjust = 0.5 if linewidth % 2 else 0
i = 1
j = 1
while gapwidth*i-adjust < width:
context.move_to(gapwidth*i-adjust, 0)
context.line_to(gapwidth*i-adjust, height)
context.stroke()
i += 1
while gapwidth*j-adjust < height:
context.move_to(0, gapwidth*j-adjust)
context.line_to(width, gapwidth*j-adjust)
context.stroke()
j += 1
class Grid(CairoVehicle):
def draw(self, context):
context.push_group()
self.make_grid(context, fgcolor=(0, 0, 0, 1), bgcolor=(1, 1, 1, 1),
gapwidth=20, linewidth=1, width=self.width,
height=self.height)
self.set_background(context.pop_group())
context.set_source(self.get_background())
context.paint()
if __name__ == "__main__":
w = gtk.Window()
w.connect("destroy", gtk.main_quit)
w.show()
cv = Grid()
cv.show()
w.add(cv)
gtk.main()
Supplemental response to comment:
Gtk.Window and Gdk.Window are conceptually different. The first is a container that can have exactly one other widget (other container or any other widget). The second is used for drawing stuff on the display and capture events. The gdk.Window is constructed in a widget's "realize" event. Before that, it is just None in PyGtk. IMHO, it is inadvisable (don't even know if it's doable) to draw on a gtk.Window.window. Custom drawing should be done in the gtk.DrawingArea.window. That is what DrawingArea is there for.
References:
https://mail.gnome.org/archives/gtk-app-devel-list/1999-January/msg00138.html
GtkDrawingArea - how to make it drawable?
Upvotes: 3