The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# Copyright 2009, 2010 Kevin Ryde

# This file is part of Chart.
#
# Chart is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# Chart 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 General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with Chart.  If not, see <http://www.gnu.org/licenses/>.


# tracking widget position in root coords, with watching for reparenting
#


package App::Chart::Gtk2::Ex::WidgetPositionTracker;
use 5.008;
use strict;
use warnings;
use Gtk2;
use Glib::Ex::SignalIds;
use Scalar::Util 1.18 'refaddr'; # 1.18 for pure-perl refaddr() fix

use App::Chart::Glib::Ex::MoreUtils;
use App::Chart::Glib::Ex::TieWeakNotify;

# uncomment this to run the ### lines
#use Smart::Comments;

use Glib::Object::Subclass
  'Glib::Object',
  properties => [Glib::ParamSpec->object
                 ('widget',
                  'widget',
                  'Blurb.',
                  'Gtk2::Widget',
                  Glib::G_PARAM_READWRITE),
                ],
  signals => { moved => { param_types => [],
                          return_type => undef },
             };

my %realized_instances;
my $configure_event_hook_id;

sub INIT_INSTANCE {
  my ($self) = @_;
}

sub FINALIZE_INSTANCE {
  my ($self) = @_;
  delete $realized_instances{refaddr($self)};
  delete $self->{'alloc_ids'};
}

sub SET_PROPERTY {
  my ($self, $pspec, $newval) = @_;
  my $pname = $pspec->get_name;

  if ($pname eq 'widget') {
    FINALIZE_INSTANCE ($self);
    delete $self->{'realize_ids'};
    App::Chart::Glib::Ex::TieWeakNotify->set ($self, $pname, $newval);

    if ($newval) {
      if ($newval->flags & 'no-window') {
        $self->{'alloc_ids'} = Glib::Ex::SignalIds->new
          ($newval,
           $newval->signal_connect (size_allocate => \&_do_size_allocate,
                                    App::Chart::Glib::Ex::MoreUtils::ref_weak($self)));
      }
      ### realized: $newval->realized
      if ($newval->realized) {
        _do_realize ($newval, \$self);
      } else {
        _connect_realize ($self, $newval);
      }
    }
  }
}

# called for size changes as well as position changes, and by the time reach
# here the old size is apparently not available any more: $allocation in
# $parameter->[1] is the same as $widget->allocation.
#
sub _do_size_allocate {
  my ($widget, $alloc, $ref_weak_self) = @_;
  ### size_allocate signal: $widget
  my $self = $$ref_weak_self || return;
  $self->signal_emit ('moved');
}

sub _connect_realize {
  my ($self, $widget) = @_;
  ### connect realize
  $self->{'realize_ids'} = Glib::Ex::SignalIds->new
    ($widget,
     $widget->signal_connect (realize => \&_do_realize,
                              App::Chart::Glib::Ex::MoreUtils::ref_weak($self)));
  ### ids: $self->{'realize_ids'}
}

sub _do_realize {
  my ($widget, $ref_weak_self) = @_;
  ### realize signal, window: $widget->window
  my $self = $$ref_weak_self || return;
  delete $self->{'realize_ids'};
  Scalar::Util::weaken ($realized_instances{refaddr($self)} = $self);
  $configure_event_hook_id ||= Gtk2::Widget->signal_add_emission_hook
    (configure_event => \&_do_configure_event);
}

sub _do_configure_event {
  my ($invocation_hint, $parameters) = @_;
  my $changed_widget = $parameters->[0];
  ### configure event: $changed_widget

  foreach my $self (values %realized_instances) {
    if (my $widget = $self->{'widget'}) {
      if ($widget->window) {
        if ($changed_widget == $widget
            || $widget->is_ancestor ($changed_widget)) {
          $self->signal_emit ('moved');
        }
      } else {
        # was unrealized
        FINALIZE_INSTANCE ($self);
        _connect_realize ($self, $widget);
      }
    } else {
      # widget weakened away
      FINALIZE_INSTANCE ($self);
    }
  }
  if (%realized_instances) {
    return 1; # stay connected
  } else {
    ### disconnect hook
    undef $configure_event_hook_id;
    return 0; # disconnect
  }
}

1;
__END__

, values %instances_nowin
        $size_allocate_id ||= Gtk2::Widget->signal_add_emission_hook
          (size_allocate => \&_do_size_allocate);
sub _do_size_allocate {
  my ($invocation_hint, $parameters) = @_;
    unless (%instances_nowin) {
      undef $size_allocate_id;
      return 0; # disconnect
    }

  my $changed_widget = $parameters->[0];
  foreach my $self (values %{$changed_widget->{(__PACKAGE__)}}) {
    $self->signal_emit ('moved');
  }
  return 1; # stay connected
}

  if (my $widget = $self->{'widget'}) {
    if (my $href = $widget->{(__PACKAGE__)}) {
      delete $href->{refaddr($self)};
      if (! %$href) {
        delete $widget->{(__PACKAGE__)};
      }
    }
  }

        my $href = ($newval->{(__PACKAGE__)} ||= {});
        $href->{refaddr($self)} = $self;
        Scalar::Util::weaken ($href->{refaddr($self)});

  my $changed_alloc = $parameters->[1];
      my $alloc = $widget->allocation;
      print $alloc->width, $changed_alloc->width,"\n";

sub _do_moved {
  my ($widget, $allocation, $ref_weak_self) = @_;
  my $self = $$ref_weak_self || return;
  $self->signal_emit ('moved');
}

    } else {
      delete $self->{'ids'};
    }

use Glib::Ex::SignalIds 5;  # version 5 for ->add()

sub _update_connections {
  my ($self) = @_;
  my $connections = $self->{'connections'};
  @$connections = ();
  my $widget = $self->{'widget'} || return;
  my $ref_weak_self = App::Chart::Glib::Ex::MoreUtils::ref_weak($self);

  if ($widget->flags & 'no-window') {
    push @$connections,
      Glib::Ex::SignalIds->new
          ($widget,
           $widget->signal_connect (size_allocate => \&_do_moved,
                                    $ref_weak_self));
  }

  do {
    my $ids = Glib::Ex::SignalIds->new
      ($widget,
       $widget->signal_connect ('notify::parent', \&_do_reparent,
                                $ref_weak_self));
    if ($widget == $self->{'widget'}
        || ! ($widget->flags & 'no-window')) {
      $ids->add ($widget->signal_connect (size_allocate => \&_do_move,
                                          $ref_weak_self));
    }
    push @$connections, $ids;

  } while ($widget = $widget->get_parent);
}

sub _do_reparent {
  my $ref_weak_self = $_[-1];
  my $self = $$ref_weak_self || return;
  _update_connections ($self);
  $self->signal_emit ('moved');
}

sub _do_moved {
  my $ref_weak_self = $_[-1];
  my $self = $$ref_weak_self || return;
  $self->signal_emit ('moved');
}