Glib-Ex-ConnectProperties
view release on metacpan or search on metacpan
lib/Glib/Ex/ConnectProperties.pm view on Meta::CPAN
# Copyright 2007, 2008, 2009, 2010, 2011, 2012, 2014, 2017 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/>.
# maybe:
# multiplier factor [$obj,'prop',mul=>2]
# get_method => name or subr
# set_method => name or subr
# paramspec => $pspec
package Glib::Ex::ConnectProperties;
use 5.008;
use strict;
use warnings;
use Carp;
use Glib;
use Scalar::Util;
use Module::Load;
use Glib::Ex::SignalIds 5; # version 5 for add()
our $VERSION = 20;
# uncomment this to run the ### lines
#use Smart::Comments;
# Hard/weak refs are as follows.
#
# * Readable property in new() permanent linkage -- the $object signal
# connection has a hard ref to $elem, and $elem->{'self'} has a hard ref
# to $self, so $elem is kept alive while $object lives. The entry for
# $elem within connp $self->{'array'} is weak so that $elem goes away when
# $object is destroyed.
#
# * Readable property in dynamic() linkage -- $elem->{'self'} is weak, which
# means $self can be garbage collected. Each $elem is still kept alive by
# the signal connection, but $self->DESTROY drops those connections.
#
# * Write-only property -- there's no signal connection, and $self has a
# hard ref to $elem, with nothing from $elem back to $self. The
# write-onlys don't keep $self alive, only the readables. Once the last
# readable object is destroyed the $self and write-onlys are destroyed.
#
# In all cases $elem->{'object'} is only a weak ref to the target $object so
# a ConnectProperties never keeps a target object alive.
#
# When $self->{'array'} gets down to just one element (one readable one)
# it'd be possible to discard it as there's nowhere for its "notify" to
# propagate values to. But maybe an add() could be made to extend an
# existing linkage, in which case would still want that last element. Maybe
# could go dynamic() style when down to one element, so if nothing else
# cares about the linkage then destroy the lot.
#
sub new {
my ($class, @array) = @_;
### ConnectProperties new()
if (@array < 2) {
croak 'ConnectProperties: new() must have two or more object/property pairs';
}
# validate property names before making signal connections
foreach my $elem (@array) {
my ($object, $pname, @params) = @$elem;
# for reference ParamSpec demands pname first char [A-Za-z] and then any
# non [A-Za-z0-9-] crunched by canonical_key() to "-"s
my $flavour;
if ($pname =~ /(.*?)#(.*)/) {
$pname = $2;
($flavour = $1) =~ tr/-/_/;
} else {
$flavour = 'object';
}
my $elem_class = "Glib::Ex::ConnectProperties::Element::$flavour";
### $elem_class
Module::Load::load ($elem_class);
# replacing element in @array
$elem = $elem_class->new (object => $object,
pname => $pname,
@params);
$elem->check_property;
}
my $self = bless { array => \@array }, $class;
my $first_readable_elem;
foreach my $elem (@array) {
if (my $h = delete $elem->{'hash_in'}) {
### hash_in func: "@{[keys %$h]}"
$elem->{'func_in'} = _make_hash_func ($h);
}
if (my $h = delete $elem->{'hash_out'}) {
### hash_out func: "@{[keys %$h]}"
$elem->{'func_out'} = _make_hash_func ($h);
}
if (delete $elem->{'bool_not'}) {
$elem->{'func_in'} = $elem->{'func_out'} = \&_bool_not;
}
Scalar::Util::weaken ($elem->{'object'});
if (! delete $elem->{'write_only'} && $elem->is_readable) {
lib/Glib/Ex/ConnectProperties.pm view on Meta::CPAN
}
return $pspec->value_validate ($value);
}
# Perl-Glib 1.240, value_validate() good
: 'value_validate');
# 'notify' or read_signal handler from a connected object
sub _do_read_handler {
my $from_elem = $_[-1];
my $self = $from_elem->{'self'};
### ConnectProperties _do_read_handler: "$self $_[0]/" . ($from_elem->{'pname'} || '[false]')
### notify_in_progress: $self->{'notify_in_progress'}
if ($self->{'notify_in_progress'}) { return; }
local $self->{'notify_in_progress'} = 1;
my $from_val = $from_elem->get_value;
### from_value to propagate: $from_val
if (my $func = $from_elem->{'func_out'}) {
$from_val = $func->($from_val);
### func_out becomes: $from_val
}
my $array = $self->{'array'};
for (my $i = 0; $i < @$array; $i++) {
my ($to_elem, $to_object);
unless (($to_elem = $array->[$i])
&& ($to_object = $to_elem->{'object'})) {
### elem gone, dropping: $i
splice @$array, $i--, 1;
next;
}
if ($to_elem == $from_elem # not ourselves
|| $to_elem->{'read_only'}) { # forced not write
next;
}
my $to_pspec = $to_elem->find_property
|| do {
### no to_pspec (such as no container child property yet, etc)
next;
};
my $to_flags = $to_pspec->get_flags;
# skip non-writable targets
($to_flags & 'writable') || next;
my $to_val = $from_val;
if (my $func = $to_elem->{'func_in'}) {
$to_val = $func->($to_val);
### func_in becomes: $to_val
}
# value_validate() to clamp $to_val for $to_pspec
# value_validate() is wrapped in Glib 1.220, remove the check when ready
# to demand that version
# In 1.240 may have to keep a new non ref counted boxed return from
# func_in() alive if value_validate() makes an alias, hence
# $to_val_keepalive.
#
my $to_val_keepalive = $to_val;
(undef, $to_val) = $to_pspec->$value_validate_method($to_val);
# skip if target already contains $to_val, to avoid extra 'notify's
if ($to_flags & 'readable') {
if (_pspec_equal ($to_pspec, $to_elem->get_value, $to_val)) {
### suppress already equal: "$to_object/".($to_elem->{'pname'} || '[false]')
next;
}
}
### store to: "$to_object/". ($to_elem->{'pname'} || '[false]')
$to_elem->set_value ($to_val);
}
return $from_elem->{'read_signal_return'};
}
sub _pspec_equal {
my ($pspec, $x, $y) = @_;
# Glib::Param::Boxed values_cmp() is only by pointer value, so try to do
# better by looking for an equal() or compare() method on the value type.
# This is only for the exact pspec 'Glib::Param::Boxed'. If you make a
# subclass for a flavour of boxed object you should implement a values_cmp
# for everyone to use.
#
if (ref $pspec eq 'Glib::Param::Boxed') {
my $value_type = $pspec->get_value_type; # string class name
if (my $func = $value_type->can('Glib_Ex_ConnectProperties_equal')) {
return $func->($x, $y);
}
# Gtk2::Gdk::Region and Gtk2::Gdk::Color have 'equal' (and GdkFont would
# too but it's not wrapped as of Gtk2 1.221). Gtk2::TreePath has a
# 'compare' method. Those methods don't much like undef (NULL), and
# presume that other similar methods won't either, so guard against
# that.
#
if (my $func = $value_type->can('equal')) {
if (! defined $x || ! defined $y) {
return ((defined $x) == (defined $y)); # undef==undef, else not equal
}
return $func->($x, $y);
}
if (my $func = $value_type->can('compare')) {
if (! defined $x || ! defined $y) {
return ((defined $x) == (defined $y)); # undef==undef, else not equal
}
return ($func->($x, $y) == 0);
}
}
# values_cmp() wrapped in Glib 1.220, will remove the fallback when ready
# to demand that version
my $func = ($pspec->can('values_cmp')
|| $pspec->can('Glib_Ex_ConnectProperties_values_cmp')
|| croak 'ConnectProperties: oops, where\'s the values_cmp fallback?');
return ($func->($pspec, $x, $y) == 0);
}
( run in 3.358 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )