# Copyright 2008, 2009, 2010 Kevin Ryde

# This file is part of Glib-Ex-ObjectBits.
#
# Glib-Ex-ObjectBits 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.
#
# Glib-Ex-ObjectBits 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 Glib-Ex-ObjectBits.  If not, see <http://www.gnu.org/licenses/>.

package Glib::Ex::SignalIds;
use 5.008;
use strict;
use warnings;
use Carp;
use Glib;
use Scalar::Util;

our $VERSION = 11;

sub new {
  my ($class, $object, @ids) = @_;

  # it's easy to forget the object in the call (and pass only the IDs), so
  # validate the first arg now
  (Scalar::Util::blessed($object) && $object->isa('Glib::Object'))
    or croak 'Glib::Ex::SignalIds->new(): first param must be the target object';

  my $self = bless [ $object ], $class;
  Scalar::Util::weaken ($self->[0]);
  $self->add (@ids);
  return $self;
}
sub add {
  my ($self, @ids) = @_;
  push @$self, @ids; # grep {$_} @ids;
}

sub DESTROY {
  my ($self) = @_;
  $self->disconnect;
}

sub object {
  my ($self) = @_;
  return $self->[0];
}
sub ids {
  my ($self) = @_;
  return @{$self}[1..$#$self];
}

sub disconnect {
  my ($self) = @_;

  my $object = $self->[0];
  if (! $object) { return; }  # target object already destroyed

  while (@$self > 1) {
    my $id = pop @$self;

    # might have been disconnected by $object in the course of its destruction
    if ($object->signal_handler_is_connected ($id)) {
      $object->signal_handler_disconnect ($id);
    }
  }
}

1;
__END__

=for stopwords Glib-Ex-ObjectBits Ryde SignalIds Eg

=head1 NAME

Glib::Ex::SignalIds -- hold connected signal handler IDs

=for test_synopsis my ($obj)

=head1 SYNOPSIS

 use Glib::Ex::SignalIds;
 my $ids = Glib::Ex::SignalIds->new
             ($obj, $obj->signal_connect (foo => \&do_foo),
                    $obj->signal_connect (bar => \&do_bar));

 # disconnected when object destroyed
 $ids = undef;

=head1 DESCRIPTION

C<Glib::Ex::SignalIds> holds a set of signal handler connection IDs
(integers) and the object they're on.  When the SignalIds is destroyed it
disconnects those IDs.

This is designed to make life easier when putting connections on "external"
objects which you should cleanup either in your own object destruction or
when switching to a different target.

=head2 Target Object Usage

A typical use is connecting to signals on a target object which is in one of
your properties.  For example a C<Gtk2::TreeModel> target in a viewer, or a
C<Gtk2::Adjustment> for scrolling.  The C<SET_PROPERTY> in your new class
might look like

    sub SET_PROPERTY {
      my ($self, $pspec, $newval) = @_;
      my $pname = $pspec->get_name;
      $self->{$pname} = $newval;  # per default GET_PROPERTY

      if ($pname eq 'model') {
        my $model = $newval;
        $self->{'model_ids'} = $model && Glib::Ex::SignalIds->new
            ($model,
             $model->signal_connect
               (row_inserted => \&my_insert_handler, $self),
             $model->signal_connect
               (row_deleted  => \&my_delete_handler, $self));
      }
    }

The C<$model &&> part allows C<undef> for no model, in which case the
C<model_ids> becomes C<undef>.  Any previous SignalIds object in
C<model_ids> is discarded and thus disconnects the previous model.  (In real
code you won't want C<$self> in the signal user data, but something weakened
to avoid a circular reference, the same as for all signal connections.)

The key to this kind of usage is that the target object might change and you
want a convenient way to connect to the new and disconnect from the old.  If
instead a sub-object or sub-widget belongs exclusively to you, never
changes, and is destroyed at the same time as your object, then there's no
need for disconnection and you don't need a SignalIds.

=head2 Weakening

SignalIds only keeps a weak reference to the target object, letting whoever
or whatever has connected the IDs manage the target lifetime.  In particular
this weakening means a SignalIds object can be kept in the instance data of
the target object itself without creating a circular reference.

If the target object is destroyed then all its signals are disconnected.
SignalIds knows no explicit disconnects are needed in that case.  SignalIds
also knows some forms of weakening and Perl's "global destruction" phase can
give slightly odd situations where the target object has disconnected its
signals but Perl hasn't yet zapped references to the object.  For that
reason SignalIds checks whether IDs are still connected before
disconnecting, to avoid warnings from Glib.

=head1 FUNCTIONS

=over 4

=item C<< $sigids = Glib::Ex::SignalIds->new ($object, $id1,$id2,...) >>

Create and return a SignalIds object holding the given C<$id> signal handler
IDs (integers) which are connected on C<$object> (a C<Glib::Object>).

SignalIds doesn't actually connect handlers.  You do that with
C<signal_connect> etc in the usual ways and all the various possible
"before", "after", user data, detail, etc, then just pass the resulting ID
to SignalIds to look after. Eg.

    my $sigids = Glib::Ex::SignalIds->new
        ($obj, $obj->signal_connect (foo => \&do_foo),
               $obj->signal_connect_after (bar => \&do_bar));

=item C<< $sigids->add ($id1, $id2, ...) >>

Add further signal IDs to C<$sigids> for the C<$object>.  This can be a
further connection made later on, or only conditionally.

    my $sigids = Glib::Ex::SignalIds->new ($obj);
    $sigids->add ($obj->signal_connect (foo => \&do_foo));
    $sigids->add ($obj->signal_connect (bar => \&do_bar));

Adding IDs one by one is good if one of the C<signal_connect> calls might
error out.  Previous connections are safely in the C<$sigids> and will be
cleaned up, whereas in a multiple-ID call some could leak on an error.  An
error making a connection is unlikely, unless perhaps the signal name comes
in externally, or the target object class hasn't been checked.

=item C<< $object = $sigids->object() >>

Return the object held in C<$sigids>, or C<undef> if it's been destroyed
(zapped by weakening).

=item C<< @ids = $sigids->ids() >>

Return a list of the signal IDs held in C<$sigids> (possibly an empty list
if nothing held).

=item C<< $sigids->disconnect() >>

Disconnect the signal IDs held in C<$sigids>, if not already disconnected.
This is done automatically when C<$sigids> is garbage collected, but you can
do it explicitly sooner if desired.

=back

=head1 SEE ALSO

L<Glib::Object>,
L<Glib::Ex::SourceIds>,
L<Glib::Ex::SignalBits>

=head1 HOME PAGE

L<http://user42.tuxfamily.org/glib-ex-objectbits/index.html>

=head1 LICENSE

Copyright 2008, 2009, 2010 Kevin Ryde

Glib-Ex-ObjectBits 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.

Glib-Ex-ObjectBits 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
Glib-Ex-ObjectBits.  If not, see L<http://www.gnu.org/licenses/>.

=cut
