Search code examples
scrollgtkdrawingcairogtk2

Gtk2 GtkDrawingArea Cairo drawing persistence when scrolled out of range


I'm drawing on a GtkDrawingArea in a GtkScrollingArea with Cairo. First I draw a grid after the expose event, then I capture mouse signals to draw rectangles in the grid. When I scroll, the grid remains, but when I scroll out of sight of the mouse-created rectangles, they disappear, even after I scroll back into the area they were in. So for one, why does the grid remain and the rectangles do not, and what can I do about this? I could save the location of each rectangle, but in what other ways could I do this?

#!/usr/bin/perl

use strict;
use warnings;

package Gtk2::MIDIPlot;

use Gtk2;
use base 'Gtk2::DrawingArea';
use Cairo;

sub new {
  my $class = shift;
  my $this = bless Gtk2::DrawingArea->new(), $class;

  $this->signal_connect(expose_event => 'Gtk2::MIDIPlot::draw');
  $this->signal_connect(button_press_event => 'Gtk2::MIDIPlot::button_press');

  $this->set_events("button-press-mask");

  return $this;
}

sub draw {
  my $this = shift;

  $this->set_size_request(28800, 1536);
  my $thisCairo = Gtk2::Gdk::Cairo::Context->create($this->get_window());

  $thisCairo->set_line_width(1);
  $thisCairo->set_source_rgb(0.75, 0.75, 0.75);
  my $inc;
  for ($inc = 0; $inc <= 2400; $inc++) {
    $thisCairo->move_to($inc * 12, 0);
    $thisCairo->line_to($inc * 12, 1536);
  };
  for ($inc = 0; $inc <= 128; $inc++) {
    $thisCairo->move_to(0, $inc * 12);
    $thisCairo->line_to(28800, $inc * 12);
  };
  $thisCairo->stroke();
}

sub button_press {
  my $this = shift;
  my $event = shift;

  if ($event->button == 1) {
    my $x = $event->x;
    my $y = $event->y;

    my $thisCairo = Gtk2::Gdk::Cairo::Context->create($this->get_window());
    $thisCairo->rectangle($x - ($x % 12), $y - ($y % 12), 12, 12);
    $thisCairo->fill();
    $thisCairo->stroke();
  };
}

package main;

use Gtk2 -init;

my $window = Gtk2::Window->new();
my $mainWidgetScroll = Gtk2::ScrolledWindow->new();
my $mainWidget = Gtk2::MIDIPlot->new();
$mainWidgetScroll->add_with_viewport($mainWidget);
$window->add($mainWidgetScroll);

Solution

  • I created a global array of objects to place, and use the expose callback function to draw the objects in that array. The expose signal handler must be used for all persistent drawing.

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    package Gtk2::MIDIPlot;
    
    use Gtk2;
    use base 'Gtk2::DrawingArea';
    use Cairo;
    
    my $gtkObjects = [];
    
    sub new {
      my $class = shift;
      my $this = bless Gtk2::DrawingArea->new(), $class;
    
      $this->signal_connect(expose_event => 'Gtk2::MIDIPlot::expose');
      $this->signal_connect(button_press_event => 'Gtk2::MIDIPlot::button');
    
      $this->set_events("button-press-mask");
    
      $this->set_size_request(28800, 1536);
    
      return $this;
    }
    
    sub expose {
      my $this = shift;
    
      my $thisCairo = Gtk2::Gdk::Cairo::Context->create($this->get_window());
    
      $thisCairo->set_line_width(2);
      $thisCairo->set_source_rgb(0.75, 0.75, 0.75);
    
      my $inc = 0;
    
      for ($inc = 0; $inc <= 2400; $inc++) {
        $thisCairo->move_to($inc * 12, 0);
        $thisCairo->line_to($inc * 12, 1536);
      };
    
      for ($inc = 0; $inc <= 128; $inc++) {
        $thisCairo->move_to(0, $inc * 12);
        $thisCairo->line_to(28800, $inc * 12);
      };
    
      $thisCairo->stroke();
    
      $thisCairo->set_source_rgb(0, 0, 0);
      if(@{$gtkObjects}) {
        foreach(@{$gtkObjects}) {
          if(@{$_}[0] eq 'rect') {
            my ($x, $y) = (@{$_}[1], @{$_}[2]);
    
            $thisCairo->rectangle($x - ($x % 12), $y - ($y % 12), 12, 12);
            $thisCairo->fill();
          };
        };
      };
    
      $thisCairo->stroke();
    }
    
    sub button {
      my $this = shift;
      my $event = shift;
    
      if ($event->button == 1) {
        my $x = $event->x;
        my $y = $event->y;
    
        push(@{$gtkObjects}, ['rect', $x, $y]);
        $this->expose;
      };
    }
    
    package main;
    
    use Gtk2 -init;
    
    my $window = Gtk2::Window->new();
    my $mainWidgetScroll = Gtk2::ScrolledWindow->new();
    my $mainWidget = Gtk2::MIDIPlot->new();
    $mainWidgetScroll->add_with_viewport($mainWidget);
    $window->add($mainWidgetScroll);