summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorJohn Stowers <john.stowers@gmail.com>2010-10-16 12:53:07 +1300
committerJohn Stowers <john.stowers@gmail.com>2010-10-16 12:53:07 +1300
commit3f0d8b9716deb2621f9aef2c81f156a03dfb3b57 (patch)
treec537357ccbf5e246f53e0d2d63b0b4c1d26de80c /examples
parent56aa50322dee974298dc0dba61c8182f2495ff58 (diff)
downloadpygtk-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
Diffstat (limited to 'examples')
-rw-r--r--examples/gtk/mirror.py328
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()
+