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

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



# $elem->do_read_handler()
# Glib::Ex::ConnectProperties::do_read_handler(..., $elem)
# \&Glib::Ex::ConnectProperties::read_handler
#
# connect_signals()
# disconnect()


package Glib::Ex::ConnectProperties::Element;
use 5.008;
use strict;
use warnings;

use Carp;
our @CARP_NOT = ('Glib::Ex::ConnectProperties');

our $VERSION = 19;

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


sub new {
  my $class = shift;
  return bless { @_ }, $class;
}

# default no properties
use constant pspec_hash => {};

sub find_property {
  my ($self) = @_;
  return $self->pspec_hash->{$self->{'pname'}};
}
sub check_property {
  my ($self) = @_;
  ### Element check_property() ...
  if (! defined $self->find_property) {
    croak("ConnectProperties: ", $self->{'object'},
          " has no property ", $self->{'pname'});
  }
}

# not documented yet
sub is_readable {
  my ($self) = @_;
  ### Element is_readable() ...
  my $pspec;
  return (! ($pspec = $self->find_property)
          || ($pspec->get_flags & 'readable'));
}

# not documented yet
sub is_writable {
  my ($self) = @_;
  ### Element is_writable() ...
  my $pspec;
  return (! ($pspec = $self->find_property)
          || ($pspec->get_flags & 'writable'));
}

# $self in signal data is a circular reference, but ConnectProperties
# disconnect() calls the disconnect() here to undo
#
# not documented yet
sub connect_signals {
  my ($self) = @_;
  ### Element connect_signals() ...
  my $object = $self->{'object'};
  my $ids = $self->{'ids'} = Glib::Ex::SignalIds->new ($object);
  foreach my $signame (delete $self->{'read_signal'} || $self->read_signal) {
    ### $signame
    $ids->add ($object->signal_connect
               ($signame,
                \&Glib::Ex::ConnectProperties::_do_read_handler,
                $self));
  }
}

# not documented yet
sub disconnect {
  my ($self) = @_;
  delete $self->{'ids'};
}

sub get_value {
  my ($self) = @_;
  croak ("ConnectProperties: cannot get value from ", $self->{'object'},
         " property '", $self->{'pname'}, "'");
}
sub set_value {
  my ($self) = @_;
  croak ("ConnectProperties: cannot set value on ", $self->{'object'},
         " property '", $self->{'pname'}, "'");
}

1;
__END__

# sub value_validate {
#   my ($self, $value) = @_;
#   if (my $pspec = $self->find_property) {
#     # value_validate() is wrapped in Glib 1.220, remove the check when ready
#     # to demand that version
#     if (my $coderef = $pspec->can('value_validate')) {
#       (undef, $value) = $pspec->$coderef($value);
#     }
#   }
#   return $value;
# }
#
# sub value_equal {
#   my ($self, $v1, $v2) = @_;
#   if (my $pspec = $self->find_property) {
#     return _pspec_equal ($pspec, $v1, $v2);
#   }
#   return $v1 eq $v2;
# }

=for stopwords Glib-Ex-ConnectProperties hashref pspecs boolean ParamSpec runtime Gtk2 lookup subclasses Subclasses ConnectProperties Ryde

=head1 NAME

Glib::Ex::ConnectProperties::Element -- special property handling

=head1 SYNOPSIS

 package Glib::Ex::ConnectProperties::Element::some_thing;
 use base 'Glib::Ex::ConnectProperties::Element';

=head1 DESCRIPTION

This is the base class for special properties in
L<Glib::Ex::ConnectProperties>.  A subclass for a property such as

    some-thing#foo

should be a class

    package Glib::Ex::ConnectProperties::Element::some_thing;
    use base 'Glib::Ex::ConnectProperties::Element';

An Element object is a hashref created from
C<Glib::Ex::ConnectProperties-E<gt>new()> etc with property name and
options.

    { object  => $obj,       # Glib::Object
      pname   => $string,    # property name "foo"
      ids     => $signalids, # signal connections
      ... further options
    }

A subclass should as a minimum implement the following methods, described
below.

    find_property() or pspec_hash()
    read_signal()
    get_value()            # if readable
    set_value()            # if writable

As an example, the simplest subclass is
C<Glib::Ex::ConnectProperties::Element::object> which is the code for plain
object C<get_property()> etc.  It can be seen how C<find_property()> looks
at the object property pspecs and C<read_signal()> is the notify, then
simply C<get_property()> or C<set_property()> to manipulate the value.

C<Glib::Ex::ConnectProperties::Element::textbuffer> is an example of
read-only access to non-property characteristics of a text buffer.  It has a
fixed set of property names in C<pspec_hash()>, and in fact shares the
boolean for "empty" and "not-empty" because the ParamSpec name field doesn't
matter, only the type etc.

C<Glib::Ex::ConnectProperties::Element::screen_size> is another read-only,
with some runtime adaption to notice when Gtk2 is new enough to have the
C<monitors-changed> signal for screen size in millimetres changing.

=head1 METHODS

=head2 Mandatory Methods

=over

=item C<$pspec = $elem-E<gt>find_property()>

=item C<$hashref = $elem-E<gt>pspec_hash()>

C<find_property()> should return a C<Glib::ParamSpec> object for the
property name C<$elem-E<gt>{'pname'}>, or return C<undef> if no such
property.

The default C<find_property()> implementation calls C<pspec_hash()>.  That
method should return a hashref name to ParamSpec

   { pname => $pspec,
     ...
   }

in which to lookup C<$elem-E<gt>{'pname'}>.  C<pspec_hash()> suits
subclasses with a fixed set of property names.  Subclasses where it varies
should implement some sort of C<find_property()>.

In each ParamSpec the following fields should be set

    flags       "readable" and/or "writable"
    type        for object, boxed, enum, flags

and for writable properties

    min,max     on int, float

The type of the ParamSpec, such as C<Glib::Param::Int> etc, is used to
know what sort of value comparison and validation should be done in the
ConnectProperties propagation.  For example C<Glib::Param::Int> properties
are compared using C<==> whereas strings are compared with C<eq>.

=item C<@signal_names = $elem-E<gt>read_signal()>

Return the name of a signal on C<$self-E<gt>{'object'}> to use to listen for
changes to the property value.

The signal name may vary with the property name C<$self-E<gt>{'pname'}>, as
for example the plain object properties using C<notify>,

    sub read_signal {
      my ($self) = @_;
      return 'notify::'.$self->{'pname'};
    }

For some element subclasses there might be a single signal for the changes
in a set of related properties,

    use constant read_signal => 'foo-changed';

C<read_signal()> is only used for readable properties.  The C<read_signal>
option to ConnectProperties can override it.

The return from C<read_signal()> is actually a list of signal names.  If a
class doesn't need any signal at all on C<$self-E<gt>{'object'}> then return
an empty list.

    use constant read_signal => ();

=item C<$value = $elem-E<gt>get_value()>

=item C<$elem-E<gt>set_value($value)>

Get or set the value of the C<$elem-E<gt>{'pname'}> property on
C<$elem-E<gt>{'object'}>.

This is usually a method call on the object, some attribute it doesn't offer
as a real property or which is more convenient when transformed in some way.

An element subclass will generally look up or derive a method name from
C<$elem-E<gt>{'pname'}>.

    my %get_method = (width  => 'get_width',
                      height => 'get_height');
    sub get_value {
      my ($self) = @_;
      my $method = $method{$self->{'pname'}};
      return $self->{'object'}->$method;
    }

=back

=head2 Optional Methods

=over

=item C<$elem-E<gt>check_property()>

Check that C<$elem-E<gt>{'pname'}> is a valid property for the target
C<$elem-E<gt>{'object'}>.  Croak if it's not.

This is called during element initialization.  The base implementation does
C<$elem-E<gt>find_property()> and if that's C<undef> then croaks with "no
such property".  An element subclass might make additional checks, or might
give a better explanation if perhaps a property is sometimes available and
sometimes not.

It's suggested that an element class should not check the class of the
target C<$elem-E<gt>{'object'}>, but only perhaps for availability of
required methods, signals, etc.  That allows a similar object with
compatible features to be used with the element class.

=back

=head1 SEE ALSO

L<Glib::Ex::ConnectProperties>

L<Glib::Object>,
L<Glib::ParamSpec>

=head1 HOME PAGE

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

=head1 LICENSE

Copyright 2010, 2011, 2012 Kevin Ryde

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

=cut