[pygtk] Add Offscreen Window (GtkMirrorBind) example



commit 3f0d8b9716deb2621f9aef2c81f156a03dfb3b57
Author: John Stowers <john stowers gmail com>
Date:   Sat Oct 16 12:53:07 2010 +1300

    Add Offscreen Window (GtkMirrorBind) example
    
    	* Shows a full container implementation in python
    	* Shows how to poke non-wrappable APIs using ctypes
    	* Fixes bug 607394

 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 0000000..877b725
--- /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()
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]