Created
January 19, 2010 00:08
-
-
Save nzjrs/280520 to your computer and use it in GitHub Desktop.
PyGtk client side windows demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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