On Sat, 2010-04-24 at 22:21 +0200, Torsten Schoenfeld wrote: > If anyone is looking for a challenge, we also still need bindings for > the offscreen window stuff:
The first patch change the way damage events are blessed, they were blessed as Gtk2::Gdk::Event::Damage but it doesn't have the ->area method. Damage events are really just expose events. Similar cases in Gtk2::Gdk::Event use a common package and are distinguished by using ->type. As we can't rename Gtk2::Gdk::Event::Expose, I chose to use it as the common package and bless damage event as a Gtk2::Gdk::Event::Expose. The second patch wrap the offscreen gdkwindow stuff. I'm wondering if offscreen gdkwindows should be in their own package. I also added a small comment to gdk_window_geometry_changed, as though it can be used with normal gdkwindows, it seems it is only useful for offscreen ones. I haven't found a way to test the from-embedder/to-embedder marshaller. The third patch contains 3 examples for offscreen gdkwindows. Two are inspired from the the gtk examples, the reflection one has small differences, I significantly expanded the rotation example to make it much more fun and generic by using the same matrix for nearly everything. (btw a $matrix->copy could be nice, I've used $matrix->multiply( Cairo::Matrix->init_identity ) instead.) And I've added a third example that simply scale the widget to the size of the window. I don't know if all 3 examples should be included. One thing I'm wondering, I've used $offscreen->set_user_data($self->window->get_user_data); to "attach" the offscreen window to its widget (so that the widget receive the expose events). GdkWindows use their user_data to hold a reference to its widget. It's the only way I found to do that, and though it works fine, maybe there should be a better way. And by the way, it would be nice if there was a way to get the widget of a gdkwindow (to be able to pick a widget with the mouse for example), the only way I know of doing that is iterating through all the widgets, and it would not work with gdkwindows that are not a $widget->window. Also it would make accessing the widget's data from the gdkwindow from-embedder/to-embedder callback much cleaner. One thing I haven't managed to do, is make it work if the child of the offscreen-using container is added/replaced after the container is realized. Quentin
>From d00c8d928d239ecbaaba14aa76749952b170be7b Mon Sep 17 00:00:00 2001 From: Quentin Sculo <squen...@free.fr> Date: Wed, 12 May 2010 00:34:48 +0200 Subject: [PATCH 1/3] make Damage Event a Gtk2::Gdk::Event::Expose object --- xs/GdkEvent.xs | 3 +-- 1 files changed, 1 insertions(+), 2 deletions(-) diff --git a/xs/GdkEvent.xs b/xs/GdkEvent.xs index 317868b..cf636dc 100644 --- a/xs/GdkEvent.xs +++ b/xs/GdkEvent.xs @@ -45,7 +45,6 @@ gdk_event_get_package (GType gtype, GdkEvent * event) { PERL_UNUSED_VAR (gtype); - switch (event->type) { case GDK_NOTHING: case GDK_DELETE: @@ -110,7 +109,7 @@ gdk_event_get_package (GType gtype, #endif #if GTK_CHECK_VERSION (2, 14, 0) case GDK_DAMAGE: - return "Gtk2::Gdk::Event::Damage"; + return "Gtk2::Gdk::Event::Expose"; #endif default: { -- 1.6.4.4
>From 7d7a3a8724ff0453f690819bceadfd086b8ba12e Mon Sep 17 00:00:00 2001 From: Quentin Sculo <squen...@free.fr> Date: Wed, 12 May 2010 00:35:00 +0200 Subject: [PATCH 2/3] wrap offscreen_window functions --- t/GdkWindow.t | 12 ++++++- xs/GdkWindow.xs | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 4 deletions(-) diff --git a/t/GdkWindow.t b/t/GdkWindow.t index 4b58b68..546a816 100644 --- a/t/GdkWindow.t +++ b/t/GdkWindow.t @@ -1,6 +1,6 @@ #!/usr/bin/perl -w use strict; -use Gtk2::TestHelper tests => 43; +use Gtk2::TestHelper tests => 45; # $Id$ @@ -327,7 +327,7 @@ SKIP: { } SKIP: { - skip 'new 2.18 stuff', 3 + skip 'new 2.18 stuff', 5 unless Gtk2->CHECK_VERSION(2, 18, 0); my $window = Gtk2::Gdk::Window -> new(undef, { window_type => 'toplevel' }); @@ -342,6 +342,14 @@ SKIP: { my $sibling = Gtk2::Gdk::Window -> new(undef, { window_type => 'toplevel' }); $window -> restack(undef, TRUE); $window -> restack($sibling, TRUE); + + my $gtkwindow= Gtk2::Window->new; + $gtkwindow->show_all; + $gtkwindow->realize; + my $offscreen= Gtk2::Gdk::Window->new(undef, { window_type => 'offscreen', }); + $offscreen->set_embedder($gtkwindow->window); + isa_ok($offscreen->get_pixmap, 'Gtk2::Gdk::Pixmap'); + isa_ok($offscreen->get_embedder,'Gtk2::Gdk::Window'); } $window -> hide(); diff --git a/xs/GdkWindow.xs b/xs/GdkWindow.xs index 8ceea10..3eafd8d 100644 --- a/xs/GdkWindow.xs +++ b/xs/GdkWindow.xs @@ -20,6 +20,7 @@ */ #include "gtk2perl.h" +#include <gperl_marshal.h> /* ------------------------------------------------------------------------- */ @@ -99,6 +100,60 @@ SvGdkWindowAttrReal (SV *object, GdkWindowAttributesType *mask) return attr; } +#if GTK_CHECK_VERSION (2, 18, 0) + +static void +gtk2perl_offscreen_coord_translate_marshal (GClosure * closure, + GValue * return_value, + guint n_param_values, + const GValue * param_values, + gpointer invocation_hint, + gpointer marshal_data) + +{ + gdouble * return_x, * return_y; + dGPERL_CLOSURE_MARSHAL_ARGS; + + GPERL_CLOSURE_MARSHAL_INIT (closure, marshal_data); + + PERL_UNUSED_VAR (return_value); + PERL_UNUSED_VAR (n_param_values); + PERL_UNUSED_VAR (invocation_hint); + + ENTER; + SAVETMPS; + + PUSHMARK (SP); + + GPERL_CLOSURE_MARSHAL_PUSH_INSTANCE (param_values); + + XPUSHs (sv_2mortal (newSViv( g_value_get_double (param_values+1) )) ); + XPUSHs (sv_2mortal (newSViv( g_value_get_double (param_values+2) )) ); + return_x = g_value_get_pointer (param_values+3); + return_y = g_value_get_pointer (param_values+4); + + GPERL_CLOSURE_MARSHAL_PUSH_DATA; + + PUTBACK; + + GPERL_CLOSURE_MARSHAL_CALL (G_ARRAY); + + if (count == 2) { + *return_y = POPn; + *return_x = POPn; + } else { + /* NOTE: croaking here can cause bad things to happen to the + * app, because croaking in signal handlers is bad juju. */ + croak ("callback must return 2 values : x and y"); + } + + PUTBACK; + FREETMPS; + LEAVE; +} +#endif + + #if 0 /* not used at the moment */ static GdkWindowAttr * SvGdkWindowAttr (SV *object) @@ -138,6 +193,20 @@ gtk2perl_gdk_window_invalidate_maybe_recurse_func (GdkWindow *window, MODULE = Gtk2::Gdk::Window PACKAGE = Gtk2::Gdk::Window PREFIX = gdk_window_ + +BOOT: + gperl_signal_set_marshaller_for (GDK_TYPE_WINDOW, "from-embedder", gtk2perl_offscreen_coord_translate_marshal); + gperl_signal_set_marshaller_for (GDK_TYPE_WINDOW, "to-embedder", gtk2perl_offscreen_coord_translate_marshal); + +=for position post_signals + +from-embedder, to-embedder and pick-embedded-child signals are for offscreen windows only. + +from-embedder and to-embedder receive the x and y coordinates to translate, and must return the translated x and y coordinate. + +=cut + + ## GdkWindow* gdk_window_new (GdkWindow *parent, GdkWindowAttr *attributes, gint attributes_mask) =for apidoc Create and return a new window. parent can be undef to mean the root @@ -956,12 +1025,37 @@ void gdk_window_flush (GdkWindow *window); gboolean gdk_window_ensure_native (GdkWindow *window); -void gdk_window_geometry_changed (GdkWindow *window); - GdkCursor_ornull * gdk_window_get_cursor (GdkWindow *window); void gdk_window_restack (GdkWindow *window, GdkWindow_ornull *sibling, gboolean above); +=for apidoc +Only useful for offscreen gdkwindows +=cut +void gdk_window_geometry_changed (GdkWindow *window); + +#endif /* 2.18 */ + + +#if GTK_CHECK_VERSION (2, 18, 0) + +MODULE = Gtk2::Gdk::Window PACKAGE = Gtk2::Gdk::Window PREFIX = gdk_offscreen_window_ + +=for apidoc +Only for offscreen gdkwindows +=cut +GdkPixmap_ornull * gdk_offscreen_window_get_pixmap (GdkWindow *offscreen_window); + +=for apidoc +Only for offscreen gdkwindows +=cut +void gdk_offscreen_window_set_embedder (GdkWindow *offscreen_window, GdkWindow *embedder); + +=for apidoc +Only for offscreen gdkwindows +=cut +GdkWindow_ornull * gdk_offscreen_window_get_embedder (GdkWindow *offscreen_window); + #endif /* 2.18 */ MODULE = Gtk2::Gdk::Window PACKAGE = Gtk2::Gdk PREFIX = gdk_ -- 1.6.4.4
>From 8eb1e2873d77eafd8bf7fc7af163d7300d37f3d0 Mon Sep 17 00:00:00 2001 From: Quentin Sculo <squen...@free.fr> Date: Wed, 12 May 2010 00:43:52 +0200 Subject: [PATCH 3/3] add offscreen examples --- examples/offscreen_reflection.pl | 156 ++++++++++++++++++++++++++++ examples/offscreen_rotation.pl | 212 ++++++++++++++++++++++++++++++++++++++ examples/offscreen_scale.pl | 130 +++++++++++++++++++++++ 3 files changed, 498 insertions(+), 0 deletions(-) create mode 100644 examples/offscreen_reflection.pl create mode 100644 examples/offscreen_rotation.pl create mode 100644 examples/offscreen_scale.pl diff --git a/examples/offscreen_reflection.pl b/examples/offscreen_reflection.pl new file mode 100644 index 0000000..b1d85e4 --- /dev/null +++ b/examples/offscreen_reflection.pl @@ -0,0 +1,156 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Glib qw(TRUE FALSE); +use Gtk2 '-init'; +use Cairo; + +my $window = Gtk2::Window->new; +$window->set_title("Reflection example"); +$window->signal_connect( delete_event => sub { exit } ); +$window->set_border_width(10); + +my $reflected= Gtk2::Ex::ReflectBin->new; + +my $entry=Gtk2::Entry->new; +my $button1=Gtk2::Button->new; +$button1->add( Gtk2::Image->new_from_stock('gtk-go-back','button') ); +$button1->signal_connect(clicked=>sub {warn "button 1 clicked\n";}); +my $button2=Gtk2::Button->new; +$button2->add( Gtk2::Image->new_from_stock('gtk-apply','button') ); +$button2->signal_connect(clicked=>sub {warn "button 2 clicked\n";}); + +my $hbox=Gtk2::HBox->new; +$hbox->pack_start($button1,FALSE,FALSE,2); +$hbox->add($entry); +$hbox->pack_start($button2,FALSE,FALSE,2); + +$reflected->add($hbox); +$window->add($reflected); +$window->show_all; + +Gtk2->main; + + + +package Gtk2::Ex::ReflectBin; +use Gtk2; +use Glib::Object::Subclass + Gtk2::EventBox::, + signals => + { size_allocate => \&do_size_allocate, + size_request => \&do_size_request, + }; + +sub INIT_INSTANCE { + my $self=shift; + $self->signal_connect( expose_event => \&expose_cb); + $self->signal_connect( damage_event => \&damage_cb); + $self->signal_connect( realize => \&realize_cb); + return $self; +} + +sub do_size_request { + my ($self,$req)=...@_; + my $border= $self->get_border_width; + return unless $self->child; + my $child_req=$self->child->size_request; + my $w= $child_req->width; + my $h= $child_req->height; + $req->width( $w +$border*2 ); + $req->height( $h*2 +$border*2 ); +} + +sub do_size_allocate { + my ($self,$alloc)=...@_; + my $border= $self->get_border_width; + my ($x,$y,$w,$h)=$alloc->values; + my $olda=$self->allocation; + $olda->x($x); $olda->width($w); + $olda->y($y); $olda->height($h); + $w-= 2*$border; + $h-= 2*$border; + $self->window->move_resize($x+$border,$y+$border,$w,$h) if $self->window; + if (my $child=$self->child) { + my $req= $child->size_request; + my $rect=Gtk2::Gdk::Rectangle->new(0,0,$w,$h/2); + $self->child->size_allocate($rect); + } + if (my $offscreen= $self->{offscreen}) { + $offscreen->move_resize(0,0,$w,$h/2); + $offscreen->geometry_changed; + } +} + +sub damage_cb { + my ($self,$event)=...@_; + # invalidate the whole window, it could be better to invalidate $event->area and the rectangle containing its reflection + $self->window->invalidate_rect(undef,0); + return 1; +} + +sub realize_cb { + my ($self)=...@_; + my $border= $self->get_border_width; + my ($x,$y,$w,$h)=$self->allocation->values; + my %attr= + ( window_type => 'offscreen', + wclass => 'output', + x => 0, + y => 0, + width => $w-$border*2, + height => ($h-$border*2)/2, + event_mask => [qw/pointer-motion-mask button-press-mask button-release-mask exposure_mask/], + ); + $self->{offscreen}= my $offscreen= Gtk2::Gdk::Window->new($self->get_root_window,\%attr); + $offscreen->set_user_data($self->window->get_user_data); + $self->window->signal_connect( pick_embedded_child =>sub { return $offscreen; }); + $self->child->set_parent_window($offscreen) if $self->child; + $offscreen->set_embedder($self->window); + $offscreen->signal_connect( to_embedder => sub {my ($offscreen,$x,$y)=...@_; return $x,$y }); + $offscreen->signal_connect( from_embedder=> sub {my ($offscreen,$x,$y)=...@_; return $x,$y }); + $self->style->set_background($offscreen,'normal'); + $offscreen->show; +} + +sub expose_cb { + my ($self,$event)=...@_; + my $offscreen= $self->{offscreen}; + if ($event->window == $self->window) { + my $pixmap = $offscreen->get_pixmap; + return 1 unless $pixmap && $self->child; + my (undef,$height)= $pixmap->get_size; + my $cr=Gtk2::Gdk::Cairo::Context->create($self->window); + my $child_alloc= $self->child->allocation; + $cr->rectangle($event->area); + $cr->clip; + $cr->save; # the above clip is used for both draw + # draw normal version on upper half (0..$height) + $cr->rectangle($child_alloc); + $cr->clip; + $cr->set_source_pixmap($pixmap,0,0); + $cr->paint; + $cr->restore; + # draw reflection on lower half ($height..$height*2) + my $mask= Cairo::LinearGradient->create(0,$height,0,$height*2); + $mask->add_color_stop_rgba(0, 0, 0, 0, 1 ); + $mask->add_color_stop_rgba(0.25, 0, 0, 0, 0.5 ); + $mask->add_color_stop_rgba(0.5, 0, 0, 0, 0.25); + $mask->add_color_stop_rgba(0.75, 0, 0, 0, 0.1 ); + $mask->add_color_stop_rgba(1.0, 0, 0, 0, 0 ); + my $matrix= Cairo::Matrix->init(1, 0, .3, 1, 0, 0); + $matrix->translate(-10,$height*2); + $matrix->scale(1,-1); + $cr->transform($matrix); + $mask->set_matrix($matrix); + $cr->rectangle($child_alloc); + $cr->clip; + $cr->set_source_pixmap($pixmap,0,0); + $cr->mask($mask); + } + elsif ($event->window == $offscreen) { + $self->propagate_expose($self->child,$event) if $self->child; + } + 1; +} + diff --git a/examples/offscreen_rotation.pl b/examples/offscreen_rotation.pl new file mode 100644 index 0000000..19d5782 --- /dev/null +++ b/examples/offscreen_rotation.pl @@ -0,0 +1,212 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Glib qw(TRUE FALSE); +use Gtk2 '-init'; +use Cairo; + +my $window = Gtk2::Window->new; +$window->set_title("Rotation example"); +$window->signal_connect( delete_event => sub { exit } ); +$window->set_border_width(5); + +my $rotated= Gtk2::Ex::RotatedBin->new; + +my $entry=Gtk2::Entry->new; +my $button=Gtk2::Button->new_from_stock('gtk-ok'); +$button->signal_connect(clicked=>sub {warn "clicked\n";}); + +my $checkh=Gtk2::CheckButton->new('mirror horizontally'); +$checkh->signal_connect(toggled=>sub { $rotated->set_mirror( horizontal=> $_[0]->get_active ) }); +my $checkv=Gtk2::CheckButton->new('mirror vertically'); +$checkv->signal_connect(toggled=>sub { $rotated->set_mirror( vertical=> $_[0]->get_active ) }); + +my $adj=Gtk2::Adjustment->new(10, 0, 360, 1,10,0); +$adj->signal_connect(value_changed=> sub { $rotated->set_angle( $_[0]->value ); }); +my $scale=Gtk2::HScale->new($adj); + +my $vbox=Gtk2::VBox->new; +$vbox->add($_) for $entry,$button,$checkh,$checkv,$scale; + +$rotated->add($vbox); +$window->add($rotated); +$window->show_all; + +#Gtk2::Gdk::Window->set_debug_updates(1); +Gtk2->main; + + +#FIXME the child needs to be added before the RotatedBin is realized +# I haven't managed to make it work if the child is added or replaced after +package Gtk2::Ex::RotatedBin; +use Gtk2; +use List::Util qw/min max/; +use Glib::Object::Subclass + Gtk2::EventBox::, + signals => + { size_allocate => \&do_size_allocate, + size_request => \&do_size_request, + }; +use constant PI => 4 * atan2(1,1); # needed for the rotation + +sub INIT_INSTANCE { + my $self=shift; + $self->{angle}=10; + $self->signal_connect( expose_event => \&expose_cb); + $self->signal_connect( damage_event => \&damage_cb); + $self->signal_connect( realize => \&realize_cb); + return $self; +} + +sub do_size_request { + my ($self,$req)=...@_; + my $border= $self->get_border_width; + my $child_req=$self->child->size_request; + my $w= $child_req->width; + my $h= $child_req->height; + my $diag= sqrt( $w**2 + $h**2 ); + $diag= 1+int $diag unless int($diag)==$diag; #round up + # request enough to satisfy the child request for any angle + $req->width( $diag+$border*2 ); + $req->height( $diag+$border*2 ); +} + +sub do_size_allocate { + my ($self,$alloc)=...@_; + my $border= $self->get_border_width; + my ($x,$y,$w,$h)=$alloc->values; + my $olda=$self->allocation; + $olda->x($x); $olda->width($w); + $olda->y($y); $olda->height($h); + $w-= 2*$border; + $h-= 2*$border; + $self->window->move_resize($x+$border,$y+$border,$w,$h) if $self->window; + $self->update_matrix; +} + +sub set_angle { + my ($self,$angle)=...@_; + $self->{angle}=$angle; + $self->update_matrix; + $self->queue_resize; +} + +sub set_mirror { + my ($self,$h_or_v,$on)=...@_; + my $key= $h_or_v eq 'vertical' ? 'vmirror' : 'hmirror'; + $self->{$key}=$on; + $self->update_matrix; + $self->queue_draw; +} + +# transform the rectangle and find a rectangle containing the transformed rectangle +sub transform_expose_rectangle { + my ($self,$rect,$inv)=...@_; + my ($x,$y,$w,$h)=$rect->values; + my $matrix= $inv ? $self->{imatrix} : $self->{matrix}; + my ($xa,$ya)=$matrix->transform_point($x, $y); + my ($xb,$yb)=$matrix->transform_point($x+$w,$y); + my ($xc,$yc)=$matrix->transform_point($x, $y+$h); + my ($xd,$yd)=$matrix->transform_point($x+$w,$y+$h); + $x= min($xa,$xb,$xc,$xd); + $y= min($ya,$yb,$yc,$yd); + $w= max($xa,$xb,$xc,$xd) -$x; + $h= max($ya,$yb,$yc,$yd) -$y; + return Gtk2::Gdk::Rectangle->new($x,$y,$w,$h); +} + +sub update_matrix { + my $self=shift; + my ($x,$y,$w,$h)= $self->allocation->values; + my $border= $self->get_border_width; + $x+=$border; $w-=2*$border; + $y+=$border; $h-=2*$border; + my $angle= $self->{angle}*PI/180; + my $matrix0=Cairo::Matrix->init_rotate($angle); + my ($xa,$ya)=$matrix0->transform_distance(0,0); + my ($xb,$yb)=$matrix0->transform_distance($w,0); + my ($xc,$yc)=$matrix0->transform_distance(0,$h); + my ($xd,$yd)=$matrix0->transform_distance($w,$h); + my $rw= $w / ( max($xa,$xb,$xc,$xd) - min($xa,$xb,$xc,$xd) ); + my $rh= $h / ( max($ya,$yb,$yc,$yd) - min($ya,$yb,$yc,$yd) ); + my $r= min($rw,$rh); + my $cw= $w*$r; + my $ch= $h*$r; + + my $matrix=Cairo::Matrix->init_identity; + $matrix->translate($w/2,$h/2); + $matrix->rotate($angle); + $matrix->translate( -$cw/2,-$ch/2 ); + if ($self->{hmirror}) { + $matrix->scale(-1,1); + $matrix->translate( -$cw,0 ); + } + if ($self->{vmirror}) { + $matrix->scale(1,-1); + $matrix->translate( 0,-$ch ); + } + $self->{matrix}=$matrix; + + my $imatrix= $matrix->multiply( Cairo::Matrix->init_identity ); #copy matrix + $imatrix->invert; + $self->{imatrix}= $imatrix; + + if (my $o=$self->{offscreen}) + { $o->{matrix}= $self->{matrix}; + $o->{imatrix}=$self->{imatrix}; + $o->move_resize(0,0,$cw,$ch); + $o->geometry_changed; + } + if (my $child=$self->child) + { my $rect=Gtk2::Gdk::Rectangle->new(0,0, $cw,$ch); + $self->child->size_allocate($rect); + } +} + +sub damage_cb { + my ($self,$event)=...@_; + my $rect= transform_expose_rectangle($self,$event->area); + $self->window->invalidate_rect( $rect, 0); + 1; +} + +sub realize_cb { + my ($self)=...@_; + my ($x,$y,$w,$h)=$self->allocation->values; + my %attr= + ( window_type => 'offscreen', + wclass => 'output', + event_mask => [qw/pointer-motion-mask button-press-mask button-release-mask exposure_mask/], + ); + $self->{offscreen}= my $offscreen= Gtk2::Gdk::Window->new($self->get_root_window,\%attr); + $self->update_matrix; # will resize $offscreen to the correct size + $offscreen->set_user_data($self->window->get_user_data); + $self->window->signal_connect( pick_embedded_child =>sub { return $offscreen; }); #could check if transformed position is inside offscreen window + $self->child->set_parent_window($offscreen) if $self->child; + $offscreen->set_embedder($self->window); + $offscreen->signal_connect( to_embedder => sub { return $_[0]{ matrix}->transform_point($_[1],$_[2]); }); + $offscreen->signal_connect( from_embedder=> sub { return $_[0]{imatrix}->transform_point($_[1],$_[2]); }); + $self->style->set_background($offscreen,'normal'); + $offscreen->show; +} + +sub expose_cb { + my ($self,$event)=...@_; + my $offscreen= $self->{offscreen}; + if ($event->window == $self->window) { + my $pixmap = $offscreen->get_pixmap; + return 1 unless $pixmap; + my $cr=Gtk2::Gdk::Cairo::Context->create($self->window); + $cr->rectangle($event->area); + $cr->clip; + $cr->set_matrix( $self->{matrix} ); + $cr->set_source_pixmap($pixmap,0,0); + $cr->paint; + } + elsif ($event->window == $offscreen) { + $self->propagate_expose($self->child,$event) if $self->child; + } + 1; +} + + diff --git a/examples/offscreen_scale.pl b/examples/offscreen_scale.pl new file mode 100644 index 0000000..2902ab3 --- /dev/null +++ b/examples/offscreen_scale.pl @@ -0,0 +1,130 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Glib qw(TRUE FALSE); +use Gtk2 '-init'; +use Cairo; + +my $window = Gtk2::Window->new; +$window->set_title("Resize me"); +$window->signal_connect( delete_event => sub { exit } ); +$window->set_border_width(5); + +my $scaled= Gtk2::Ex::ScaleBin->new; + +my $entry=Gtk2::Entry->new; +my $button=Gtk2::Button->new_from_stock('gtk-ok'); +$button->signal_connect(clicked=>sub {warn "clicked\n";}); + +my $vbox=Gtk2::VBox->new; +$vbox->add($entry); +$vbox->add($button); + +$scaled->add($vbox); +$window->add($scaled); +$window->show_all; + + +Gtk2->main; + + + +package Gtk2::Ex::ScaleBin; +use Gtk2; +use Glib::Object::Subclass + Gtk2::EventBox::, + signals => + { size_allocate => \&do_size_allocate, + }; + +sub INIT_INSTANCE { + my $self=shift; + $self->signal_connect( expose_event => \&expose_cb); + $self->signal_connect( damage_event => \&damage_cb); + $self->signal_connect( realize => \&realize_cb); + return $self; +} + +sub do_size_allocate { + my ($self,$alloc)=...@_; + my $border= $self->get_border_width; + my ($x,$y,$w,$h)=$alloc->values; + my $olda=$self->allocation; + $olda->x($x); $olda->width($w); + $olda->y($y); $olda->height($h); + $w-= 2*$border; + $h-= 2*$border; + $self->window->move_resize($x+$border,$y+$border,$w,$h) if $self->window; + my ($reqw,$reqh)= ($w,$h); + if (my $child=$self->child) { + my $req= $child->size_request; + $reqw= $req->width; + $reqh= $req->height; + my $rect=Gtk2::Gdk::Rectangle->new(0, 0, $reqw, $reqh); + $self->child->size_allocate($rect); + } + $self->{zoom_x}= $w / $reqw; + $self->{zoom_y}= $h / $reqh; + if (my $offscreen= $self->{offscreen}) { + $offscreen->{zoom_x}= $self->{zoom_x}; + $offscreen->{zoom_y}= $self->{zoom_y}; + $offscreen->move_resize(0,0, $reqw, $reqh); + $offscreen->geometry_changed; + } +} + +sub damage_cb { + my ($self,$event)=...@_; + my ($x,$y,$w,$h)=$event->area->values; + my $zx= $self->{zoom_x}; + my $zy= $self->{zoom_y}; + my $rect=Gtk2::Gdk::Rectangle->new($x*$zx, $y*$zy, $w*$zx, $h*$zy); + $self->window->invalidate_rect($rect,0); + 1; +} + +sub realize_cb { + my ($self)=...@_; + my ($x,$y,$w,$h)=$self->allocation->values; + my %attr= + ( window_type => 'offscreen', + wclass => 'output', + x => 0, + y => 0, + width => $w, + height => $h, + event_mask => [qw/pointer-motion-mask button-press-mask button-release-mask exposure_mask/], + ); + $self->{offscreen}= my $offscreen= Gtk2::Gdk::Window->new($self->get_root_window,\%attr); + $offscreen->set_user_data($self->window->get_user_data); + $self->window->signal_connect( pick_embedded_child =>sub { return $offscreen; }); + $self->child->set_parent_window($offscreen) if $self->child; + $offscreen->set_embedder($self->window); + $offscreen->{zoom_x}= $self->{zoom_x}; + $offscreen->{zoom_y}= $self->{zoom_y}; + $offscreen->signal_connect( to_embedder => sub {my ($offscreen,$x,$y)=...@_; return $x*$offscreen->{zoom_x},$y*$offscreen->{zoom_y} }); + $offscreen->signal_connect( from_embedder=> sub {my ($offscreen,$x,$y)=...@_; return $x/$offscreen->{zoom_x},$y/$offscreen->{zoom_y} }); + $self->style->set_background($offscreen,'normal'); + $offscreen->show; +} + +sub expose_cb { + my ($self,$event)=...@_; + my $offscreen= $self->{offscreen}; + if ($event->window == $self->window) { + my $pixmap = $offscreen->get_pixmap; + return 1 unless $pixmap; + my ($w,$h)= $pixmap->get_size; + my $cr=Gtk2::Gdk::Cairo::Context->create($self->window); + $cr->rectangle($event->area); + $cr->clip; + $cr->scale($self->{zoom_x},$self->{zoom_y}); + $cr->set_source_pixmap($pixmap,0,0); + $cr->paint; + } + elsif ($event->window == $offscreen) { + $self->propagate_expose($self->child,$event) if $self->child; + } + 1; +} + -- 1.6.4.4
_______________________________________________ gtk-perl-list mailing list gtk-perl-list@gnome.org http://mail.gnome.org/mailman/listinfo/gtk-perl-list