#!/usr/bin/perl -w # # Drawing Area # # GtkDrawingArea is a blank area where you can draw custom displays # of various kinds. # # This demo has two drawing areas. The checkerboard area shows # how you can just draw something; all you have to do is write # a signal handler for expose_event, as shown here. # # The "scribble" area is a bit more advanced, and shows how to handle # events such as button presses and mouse motion. Click the mouse # and drag in the scribble area to draw squiggles. Resize the window # to clear the area. # package drawingarea; use Glib qw(TRUE FALSE); use Gtk2; my $window = undef; # Pixmap for scribble area, to store current scribbles my $pixmap = undef; # Create a new pixmap of the appropriate size to store our scribbles sub scribble_configure_event { my ($widget, $event, $data) = @_; # get rid of the old one $pixmap = undef if $pixmap; $pixmap = Gtk2::Gdk::Pixmap->new ($widget->window, $widget->allocation->width, $widget->allocation->height, -1); # Initialize the pixmap to white $pixmap->draw_rectangle ($widget->style->white_gc, TRUE, 0, 0, $widget->allocation->width, $widget->allocation->height); # We've handled the configure event, no need for further processing. return TRUE; } # Redraw the screen from the pixmap sub scribble_expose_event { my ($widget, $event, $data) = @_; # # We use the "foreground GC" for the widget since it already exists, # but honestly any GC would work. The only thing to worry about # is whether the GC has an inappropriate clip region set. # $widget->window->draw_drawable ($widget->style->fg_gc($widget->state), $pixmap, # Only copy the area that was exposed. $event->area->x, $event->area->y, $event->area->x, $event->area->y, $event->area->width, $event->area->height); return FALSE; } # Draw a rectangle on the screen sub draw_brush { my ($widget, $x, $y) = @_; my $update_rect = Gtk2::Gdk::Rectangle->new ($x - 3, $y - 3, 6, 6); # Paint to the pixmap, where we store our state $pixmap->draw_rectangle ($widget->style->black_gc, TRUE, $update_rect->values); # $update_rect->x, # $update_rect->y, # $update_rect->width, # $update_rect->height); # Now invalidate the affected region of the drawing area. # $widget->window->invalidate_rect (\@update_rect, FALSE); # use Data::Dumper; # warn Dumper($update_rect); $widget->window->invalidate_rect ($update_rect, FALSE); } sub scribble_button_press_event { my ($widget, $event, $data) = @_; return FALSE unless defined $pixmap; # paranoia check, in case we haven't gotten a configure event if ($event->button == 1) { draw_brush ($widget, $event->x, $event->y); } # We've handled the event, stop processing return TRUE; } sub scribble_motion_notify_event { my ($widget, $event, $data) = @_; return FALSE unless defined $pixmap; # paranoia check, in case we haven't gotten a configure event # # This call is very important; it requests the next motion event. # If you don't call gdk_window_get_pointer() you'll only get # a single motion event. The reason is that we specified # GDK_POINTER_MOTION_HINT_MASK to gtk_widget_set_events(). # If we hadn't specified that, we could just use event->x, event->y # as the pointer location. But we'd also get deluged in events. # By requesting the next event as we handle the current one, # we avoid getting a huge number of events faster than we # can cope. # my (undef, $x, $y, $state) = $event->window->get_pointer; # if (state & GDK_BUTTON1_MASK) # if (grep 'button1-mask', @$state) { if (grep (/button1-mask/, @$state)) { draw_brush ($widget, $x, $y); } # We've handled it, stop processing return TRUE; } my $color; sub checkerboard_expose { my ($da, $event, $data) = @_; use constant CHECK_SIZE => 10; use constant SPACING => 2; # # At the start of an expose handler, a clip region of event->area # is set on the window, and event->area has been cleared to the # widget's background color. The docs for # gdk_window_begin_paint_region() give more details on how this # works. # # It would be a bit more efficient to keep these # GCs around instead of recreating on each expose, but # this is the lazy/slow way. # my $gc1 = Gtk2::Gdk::GC->new ($da->window); my $color = Gtk2::Gdk::Color->new (30000, 0, 30000); # my $color = Gtk2::Gdk::Color->parse ('purple'); $gc1->set_rgb_fg_color ($color); my $gc2 = Gtk2::Gdk::GC->new ($da->window); # $color = Gtk2::Gdk::Color->new (65535, 65535, 65535); $color = Gtk2::Gdk::Color->parse ('white'); $gc2->set_rgb_fg_color ($color); my $xcount = 0; my $i = SPACING; while ($i < $da->allocation->width) { my $j = SPACING; my $ycount = $xcount % 2; # start with even/odd depending on row while ($j < $da->allocation->height) { # # If we're outside event->area, this will do nothing. # It might be mildly more efficient if we handled # the clipping ourselves, but again we're feeling lazy. # $da->window->draw_rectangle ($ycount % 2 ? $gc1 : $gc2, TRUE, $i, $j, CHECK_SIZE, CHECK_SIZE); $j += CHECK_SIZE + SPACING; ++$ycount; } $i += CHECK_SIZE + SPACING; ++$xcount; } # g_object_unref (gc1); # g_object_unref (gc2); # # return TRUE because we've handled this event, so no # further processing is required. # return TRUE; } sub do { if (!$window) { $window = Gtk2::Window->new; $window->set_title ("Drawing Area"); $window->signal_connect (destroy => sub { $window = undef; 1 }); $window->set_border_width (8); my $vbox = Gtk2::VBox->new (FALSE, 8); $vbox->set_border_width (8); $window->add ($vbox); # # Create the checkerboard area # my $label = Gtk2::Label->new; $label->set_markup ("<u>Checkerboard pattern</u>"); $vbox->pack_start ($label, FALSE, FALSE, 0); my $frame = Gtk2::Frame->new; $frame->set_shadow_type ('in'); $vbox->pack_start ($frame, TRUE, TRUE, 0); my $da = Gtk2::DrawingArea->new; # set a minimum size $da->set_size_request (100, 100); $frame->add ($da); $da->signal_connect (expose_event => \&checkerboard_expose); # # Create the scribble area # $label = Gtk2::Label->new; $label->set_markup ("<u>Scribble area</u>"); $vbox->pack_start ($label, FALSE, FALSE, 0); $frame = Gtk2::Frame->new; $frame->set_shadow_type ('in'); $vbox->pack_start ($frame, TRUE, TRUE, 0); $da = Gtk2::DrawingArea->new; # set a minimum size $da->set_size_request (100, 100); $frame->add ($da); # Signals used to handle backing pixmap $da->signal_connect (expose_event => \&scribble_expose_event); $da->signal_connect (configure_event => \&scribble_configure_event); # Event signals $da->signal_connect (motion_notify_event => \&scribble_motion_notify_event); $da->signal_connect (button_press_event => \&scribble_button_press_event); # # Ask to receive events the drawing area doesn't normally # subscribe to # $da->set_events ([ @{ $da->get_events }, ## 'exposure-mask', 'leave-notify-mask', 'button-press-mask', 'pointer-motion-mask', 'pointer-motion-hint-mask', ]); } if (!$window->visible) { $window->show_all; } else { $window->destroy; $window = undef; } return $window; } 1; __END__ Copyright (C) 2003 by the gtk2-perl team (see the file AUTHORS for the full list) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA.