diff options
author | John Stowers <john.stowers@gmail.com> | 2010-10-16 12:53:07 +1300 |
---|---|---|
committer | John Stowers <john.stowers@gmail.com> | 2010-10-16 12:53:07 +1300 |
commit | 3f0d8b9716deb2621f9aef2c81f156a03dfb3b57 (patch) | |
tree | c537357ccbf5e246f53e0d2d63b0b4c1d26de80c | |
parent | 56aa50322dee974298dc0dba61c8182f2495ff58 (diff) | |
download | pygtk-3f0d8b9716deb2621f9aef2c81f156a03dfb3b57.tar.gz |
Add Offscreen Window (GtkMirrorBind) example
* Shows a full container implementation in python
* Shows how to poke non-wrappable APIs using ctypes
* Fixes bug 607394
-rw-r--r-- | examples/gtk/mirror.py | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/examples/gtk/mirror.py b/examples/gtk/mirror.py new file mode 100644 index 00000000..877b725a --- /dev/null +++ b/examples/gtk/mirror.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python +# Gtk+ client-side-windows demo in python. Also demonstrates how to use +# ctypes to wrap otherwise unexposed GObject API +# 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") + +DBG = True + +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)) + +def dbg(s): + if DBG: print s + +class GtkMirrorBin(gtk.Bin): + __gsignals__ = { + "damage_event" : "override" + } + + def __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): + dbg("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): + dbg("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): + dbg("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): + dbg("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): + dbg("do_add") + if not self.child: + widget.set_parent_window(self.offscreen_window) + widget.set_parent(self) + self.child = widget + else: + raise Exception("GtkMirrorBind cannot have more than one child") + + def do_remove(self, widget): + dbg("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): + dbg("do_forall") + if self.child: + callback(self.child, data) + + def do_size_request(self, r): + dbg("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): + dbg("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): + dbg("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): + dbg("do_damage") + #invalidate the whole window + self.window.invalidate_rect(None, False) + return True + + def do_expose_event(self, event): + dbg("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() + + dbg("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: + dbg("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.connect("delete-event", gtk.main_quit) + 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() + |