PyGtk client side windows demo
#!/usr/bin/env python | |
# Gtk+ client-side-windows demo in python | |
# John Stowers | |
import gobject | |
import cairo | |
import gtk | |
import gtk.gdk as gdk | |
#hacks for using functions not exposed in this pygtk version | |
import ctypes | |
cgdk = ctypes.CDLL("libgdk-x11-2.0.so") | |
class _PyGObject_Functions(ctypes.Structure): | |
_fields_ = [ | |
('register_class', | |
ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p, | |
ctypes.c_int, ctypes.py_object, | |
ctypes.py_object)), | |
('register_wrapper', | |
ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.py_object)), | |
('register_sinkfunc', | |
ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)), | |
('lookupclass', | |
ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_int)), | |
('newgobj', | |
ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)), | |
] | |
class PyGObjectCPAI(object): | |
def __init__(self): | |
addr = ctypes.pythonapi.PyCObject_AsVoidPtr( | |
ctypes.py_object(gobject._PyGObject_API)) | |
self._api = _PyGObject_Functions.from_address(addr) | |
def pygobject_new(self, addr): | |
return self._api.newgobj(addr) | |
cgobject = PyGObjectCPAI() | |
def gdk_offscreen_window_set_embedder(window, embedder): | |
cgdk.gdk_offscreen_window_set_embedder(hash(window), hash(embedder)) | |
def gdk_offscreen_window_get_pixmap(window): | |
pm = cgdk.gdk_offscreen_window_get_pixmap(hash(window)) | |
return cgobject.pygobject_new(ctypes.c_void_p(pm)) | |
class GtkMirrorBin(gtk.Bin): | |
__gsignals__ = { | |
"damage_event" : "override" | |
} | |
def __init__(self): | |
# gobject.GObject.__init__(self) | |
gtk.Bin.__init__(self) | |
self.child = None | |
self.offscreen_window = None | |
self.unset_flags(gtk.NO_WINDOW) | |
def _to_child(self, widget_x, widget_y): | |
return widget_x, widget_y | |
def _to_parent(self, offscreen_x, offscreen_y): | |
return offscreen_x, offscreen_y | |
def _pick_offscreen_child(self, offscreen_window, widget_x, widget_y): | |
print "pick_child" | |
if self.child and self.child.flags() & gtk.VISIBLE: | |
x,y = self._to_child(widget_x, widget_y) | |
ca = self.child.allocation | |
if (x >= 0 and x < ca.width and y >= 0 and y < ca.height): | |
return self.offscreen_window | |
return None | |
def _offscreen_window_to_parent(self, offscreen_window, offscreen_x, offscreen_y, parent_x, parent_y): | |
print "to_parent" | |
x,y = self._to_parent(offscreen_x, offscreen_y) | |
#FIXME: parent_x and parent_y are gpointers... | |
px = ctypes.c_void_p(hash(parent_x)) | |
px.contents = x | |
py = ctypes.c_void_p(hash(parent_y)) | |
py.contents = y | |
def _offscreen_window_from_parent(self, parent_window, parent_x, parent_y, offscreen_x, offscreen_y): | |
print "to_child" | |
x,y = self._to_child(parent_x, parent_y) | |
#FIXME: offscreen_x and offscreen_y are gpointers... | |
ox = ctypes.c_void_p(hash(offscreen_x)) | |
ox.contents = x | |
oy = ctypes.c_void_p(hash(offscreen_y)) | |
oy.contents = y | |
def do_realize(self): | |
print "do_realize" | |
self.set_flags(gtk.REALIZED) | |
border_width = self.border_width | |
w = self.allocation.width - 2*border_width | |
h = self.allocation.height - 2*border_width | |
self.window = gdk.Window( | |
self.get_parent_window(), | |
x=self.allocation.x + border_width, | |
y=self.allocation.y + border_width, | |
width=w, | |
height=h, | |
window_type=gdk.WINDOW_CHILD, | |
event_mask=self.get_events() | |
| gdk.EXPOSURE_MASK | |
| gdk.POINTER_MOTION_MASK | |
| gdk.BUTTON_PRESS_MASK | |
| gdk.BUTTON_RELEASE_MASK | |
| gdk.SCROLL_MASK | |
| gdk.ENTER_NOTIFY_MASK | |
| gdk.LEAVE_NOTIFY_MASK, | |
visual=self.get_visual(), | |
colormap=self.get_colormap(), | |
wclass=gdk.INPUT_OUTPUT) | |
self.window.set_user_data(self) | |
self.window.connect("pick-embedded-child", self._pick_offscreen_child) | |
if self.child and self.child.flags() & gtk.VISIBLE: | |
w = self.child.allocation.width | |
h = self.child.allocation.height | |
self.offscreen_window = gdk.Window( | |
self.get_root_window(), | |
x=self.allocation.x + border_width, | |
y=self.allocation.y + border_width, | |
width=w, | |
height=h, | |
window_type=gdk.WINDOW_OFFSCREEN, | |
event_mask=self.get_events() | |
| gdk.EXPOSURE_MASK | |
| gdk.POINTER_MOTION_MASK | |
| gdk.BUTTON_PRESS_MASK | |
| gdk.BUTTON_RELEASE_MASK | |
| gdk.SCROLL_MASK | |
| gdk.ENTER_NOTIFY_MASK | |
| gdk.LEAVE_NOTIFY_MASK, | |
visual=self.get_visual(), | |
colormap=self.get_colormap(), | |
wclass=gdk.INPUT_OUTPUT) | |
self.offscreen_window.set_user_data(self) | |
if self.child: | |
self.child.set_parent_window(self.offscreen_window) | |
# self.offscreen_window.set_embedder(self.window) | |
gdk_offscreen_window_set_embedder(self.offscreen_window, self.window) | |
self.offscreen_window.connect("to-embedder", self._offscreen_window_to_parent) | |
self.offscreen_window.connect("from-embedder", self._offscreen_window_from_parent) | |
self.style.attach(self.window) | |
self.style.set_background(self.window, gtk.STATE_NORMAL) | |
self.style.set_background(self.offscreen_window, gtk.STATE_NORMAL) | |
self.offscreen_window.show() | |
def do_unrealize(self): | |
self.offscreen_window.set_user_data(None) | |
self.offscreen_window = None | |
def do_add(self, widget): | |
print "do_add" | |
if not self.child: | |
widget.set_parent_window(self.offscreen_window) | |
widget.set_parent(self) | |
self.child = widget | |
else: | |
print "GtkMirrorBind cannot have more than one child" | |
def do_remove(self, widget): | |
print "do_remove" | |
was_visible = widget.flags() & gtk.VISIBLE | |
if self.child == widget: | |
widget.unparent() | |
self.child = None | |
if was_visible and (self.flags() & gtk.VISIBLE): | |
self.queue_resize() | |
def do_forall(self, internal, callback, data): | |
print "do_forall" | |
if self.child: | |
callback(self.child, data) | |
def do_size_request(self, r): | |
print "do_size_request" | |
cw, ch = 0,0; | |
if self.child and (self.child.flags() & gtk.VISIBLE): | |
cw, ch = self.child.size_request() | |
r.width = self.border_width * 2 + cw + 10 | |
r.height = self.border_width * 2 + ch * 2 + 10 | |
def do_child_type(self): | |
print "do_child_type" | |
#FIXME: This never seems to get called... | |
if self.child: | |
return None | |
return gtk.Widget.__gtype__ | |
def do_size_allocate(self, allocation): | |
print "do_size_allocate" | |
self.allocation = allocation | |
border_width = self.border_width | |
w = self.allocation.width - 2*border_width | |
h = self.allocation.height - 2*border_width | |
if self.flags() & gtk.REALIZED: | |
self.window.move_resize( | |
allocation.x + border_width, | |
allocation.y + border_width, | |
w,h) | |
if self.child and self.child.flags() & gtk.VISIBLE: | |
cw, ch = self.child.get_child_requisition() | |
ca = gtk.gdk.Rectangle(x=0,y=0,width=cw,height=ch) | |
if self.flags() & gtk.REALIZED: | |
self.offscreen_window.move_resize( | |
allocation.x + border_width, | |
allocation.y + border_width, | |
cw, ch) | |
self.child.size_allocate(ca) | |
def do_damage_event(self, eventexpose): | |
print "do_damage" | |
#invalidate the whole window | |
self.window.invalidate_rect(None, False) | |
return True | |
def do_expose_event(self, event): | |
print "do_expose" | |
if self.flags() & gtk.VISIBLE and self.flags() & gtk.MAPPED: | |
if event.window == self.window: | |
#FIXME: beware pointers... | |
pm = gdk_offscreen_window_get_pixmap(self.offscreen_window) | |
w,h = pm.get_size() | |
print "expose both %dx%d" % (w,h) | |
cr = self.window.cairo_create() | |
cr.save() | |
cr.rectangle(0,0,w,h) | |
cr.clip() | |
#paint the offscreen child | |
cr.set_source_pixmap(pm, 0, 0) | |
cr.paint() | |
cr.restore() | |
matrix = cairo.Matrix(1.0, 0.0, 0.3, 1.0, 0.0, 0.0) | |
matrix.scale(1.0, -1.0) | |
matrix.translate(-10, -3 * h - 10) | |
cr.transform(matrix) | |
cr.rectangle(0,h,w,h) | |
cr.clip() | |
cr.set_source_pixmap(pm, 0, h) | |
#create linear gradient | |
mask = cairo.LinearGradient(0.0, h, 0.0, 2*h) | |
mask.add_color_stop_rgba(0.0, 0.0, 0.0, 0.0, 0.0) | |
mask.add_color_stop_rgba(0.25, 0.0, 0.0, 0.0, 0.01) | |
mask.add_color_stop_rgba(0.5, 0.0, 0.0, 0.0, 0.25) | |
mask.add_color_stop_rgba(0.75, 0.0, 0.0, 0.0, 0.5) | |
mask.add_color_stop_rgba(1.0, 0.0, 0.0, 0.0, 1.0) | |
#paint the reflection | |
cr.mask(mask) | |
elif event.window == self.offscreen_window: | |
print "expose offscreen" | |
self.style.paint_flat_box( | |
event.window, | |
gtk.STATE_NORMAL, gtk.SHADOW_NONE, | |
event.area, self, "blah", | |
0, 0, -1, -1) | |
if self.child: | |
self.propagate_expose( | |
self.child, | |
event) | |
return False | |
gobject.type_register(GtkMirrorBin) | |
if __name__ == "__main__": | |
w = gtk.Window() | |
w.set_border_width(10) | |
hb = gtk.HBox(6) | |
hb.pack_start(gtk.Button(stock=gtk.STOCK_GO_BACK)) | |
hb.pack_start(gtk.Entry()) | |
bin = GtkMirrorBin() | |
bin.add(hb) | |
w.add(bin) | |
w.show_all() | |
gtk.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment