NAME

Glib::Ex::ConnectProperties -- link properties between objects

SYNOPSIS

use Glib::Ex::ConnectProperties;
my $conn = Glib::Ex::ConnectProperties->new
                       ([ $check,  'active' ],
                        [ $widget, 'visible' ]);

$conn->disconnect;   # explicit disconnect

DESCRIPTION

Glib::Ex::ConnectProperties links together specified properties on two or more Glib::Objects (including Gtk2 widgets) so a change made to any one of them is propagated to the others.

This is an easy way to tie a user control widget to a setting elsewhere. For example a CheckButton active could be linked to the visible of another widget, letting the user click to hide or show it.

+--------------------+             +-------------+
| CheckButton/active |  <------->  | Foo/visible |
+--------------------+             +-------------+

The advantage of ConnectProperties is that it's bi-directional, so if other code changes "Foo/visible" then that change is sent back to "CheckButton/active" too, ensuring the button display shows what it's controlling, no matter how the target changes.

Property Types

String, number, enum, flags, and object property types are supported. A few boxed types like Glib::Strv and Gtk2::Gdk::Color work too, but others may not (see "Equality" below).

Read-only properties can be given. They're propagated out to the other linked properties but changes in those others are not stored back. This can leave different values, which defeats the purpose of the linkage. A read-only probably only makes sense if that read-only is the only one changing. An explicit signal handler could propagate that but a ConnectProperties is handy and is careful not to make circular references. See the read_only option below to force read-only.

Write-only properties can be given. Nothing is read out of them, they're just set from changes in the other linked properties. Often write-only properties are pseudo "add" methods etc, so it's a little unlikely linking a write-only will be wanted. See the write_only option below to force write-only.

It works to link two properties on the same object. This can ensure they update together. It also works to have two different ConnectProperties with an object/property in common. A change coming from one group propagates through to the other. This arises quite naturally if you've got two controls for the same target -- neither needs to know the other exists.

A property name can include an explicit class like GtkLabel::justify as usual for set_property, find_property, etc. If a subclass accidentally shadows a superclass property name then this gives access to the superclass, but is otherwise unnecessary. A Perl subclass like My::Foo::Bar is My__Foo__Bar::propname, as usual for Perl module to Glib class name conversion.

FUNCTIONS

Creation

$conn = Glib::Ex::ConnectProperties->new ([$obj1,$pname1], [$obj,$pname2], ...)

Connect two or more given object+property combinations. The connection lasts for as long as the objects do.

The return value is a Perl object of type Glib::Ex::ConnectProperties. It can be kept to later break the connection with disconnect below, otherwise it can be ignored.

$conn = Glib::Ex::ConnectProperties->dynamic ([$obj1,$pname1], [$obj,$pname2], ...)

Connect two or more given object+property combinations. The return is a Perl object of type Glib::Ex::ConnectProperties. The connection lasts only as long as you keep that returned object.

The arguments to both functions are arrayrefs with an object, a property name, and possible further options described below. For example

Glib::Ex::ConnectProperties->new
  ([$aa_object, 'some-propname'],
   [$bb_object, 'another-propname']);

An initial value is propagated from the first object+property (the first readable one) to set all the others in case they're not already the same. Put the object with the desired initial value first.

A ConnectProperties only keeps weak references to the objects, so the linkage doesn't prevent some or all of them being garbage collected.

A dynamic() linkage can be used if it's only wanted for a certain time, or if the target objects to link might change and you will want to drop the old and make a new one. For example something like the following in a widget or object would allow a target to be changed, including changed to undef for nothing to link.

sub set_target {
  my ($self, $target_object) = @_;
  $self->{'conn'} =
    $target_object && Glib::Ex::ConnectProperties->new
                        ([$self,   'my-prop'],
                         [$target, 'target-prop']);
}

Operations

$conn->disconnect()

Disconnect the given ConnectProperties linkage.

This can be a linkage made by either new and dynamic above. A dynamic one is disconnected automatically when garbage collected.

OPTIONS

Various key/value options can be given in each [$object,$propname] element. For example,

Glib::Ex::ConnectProperties->new
    ([$checkbutton, 'active'],
     [$label, 'sensitive', bool_not => 1]);

General Options

read_only => $bool

Treat the property as read-only, ignoring any writable flag in its ParamSpec. This is probably of limited use, but might for instance stop other properties writing back to a master control.

write_only => $bool

Treat the property as write-only, ignoring any readable flag in its ParamSpec.

This could be used for display things such as a Gtk2::Label which you want to set, but don't want to read back. If the value is mangled for display (see "Value Transformations" below) then there might not be an easy reverse transformation to read back anyway.

Glib::Ex::ConnectProperties->new
    ([$job, 'status'],
     [$label, 'text', write_only => 1]);

Of course an explicit signal handler can do a one-way set like this, but ConnectProperties is a couple less lines.

read_signal => $signame

Connect to $signame to see changes to the property. The default notify::$propname means a property change is immediately seen and propagated. A different signal can be used to do it at other times instead.

For example on a Gtk2::Entry the text property notifies for every character typed by the user. With the activate signal you can instead take the value only when the user presses Return.

Glib::Ex::ConnectProperties->new
    ([$entry, 'text', read_signal => 'activate'],
     [$label, 'text']);

The signal can have any parameters (which are all ignored currently). Usually the only sensible signals are those like activate which are some sort of user action.

read_signal_return => $signame

The return value for the read_signal handler above. The default return is undef.

Most signals that make sense for read_signal have no return value (ie. void), so nothing particular is needed. But as an example if a widget event handler was a good time to look at a property then a return of Gtk2::EVENT_PROPAGATE would generally be wanted to let other handlers see the event too.

Glib::Ex::ConnectProperties->new
    ([$widget, 'window',
      read_signal => 'map-event',
      read_signal_return => Gtk2::EVENT_PROPAGATE ],
     [$drawing_thing, 'target-window']);

Value Transformations

The following value transformations can be specified with parameters in each object/property element. Storing a value goes through the following steps,

  1. Value transformations specified in the element, if any.

  2. value_validate() for the target ParamSpec (in Glib-Perl 1.220 where that method is available).

  3. Equality check, if the target is readable, to avoid a set if it's already what's desired (see "Equality" below).

The "in" transformations are for storing. The func_in is the most general, or the hash_in is handy for a fixed set of possible values. value_validate will then clamp numbers which might be out of range, perhaps manipulate string contents, etc. The result might then not be exactly what was desired, but at least gives something which can be stored.

bool_not => 1

Negate with the Perl ! operator. For example a check button which when checked makes a label insensitive,

Glib::Ex::ConnectProperties->new
    ([$checkbutton, 'active'],
     [$label, 'sensitive', bool_not => 1]);
func_in => $coderef
func_out => $coderef

Call $value = &$coderef($value) to transform values going in or coming out.

hash_in => $hashref
hash_out => $hashref

Apply $value = $hashref->{$value} to transform values going in or coming out.

If a $value doesn't exist in the hash then the result will be undef in the usual way. Various tie modules can change that in creative ways, for example Hash::WithDefaults to look in fallback hashes.

The hashes are not copied, so future changes to their contents will be used by the ConnectProperties, though there's nothing to forcibly update values if the current settings might be affected.

For a read-write property the "in" should generally be the inverse of "out". Nothing is done to enforce that, but strange things are likely to happen if the two are inconsistent.

A read-only property only needs an "out" transformation or a write-only property only needs an "in" transformation, including when forced to read-only or write-only with the read_only or write_only options above ("General Options").

OTHER SETTINGS

The following additional object or widget settings can be accessed by ConnectProperties. They're things which have signals notifying when they change, but are not properties as such.

The Gtk2 things don't create a dependency on Gtk2 unless you use them. The implementation is modular too so the extras are not loaded unless used. The # separator character used here is not allowed in ParamSpec names, so these extra forms shouldn't clash with plain object property names.

Widget Allocation

$widget->allocation fields on a Gtk2::Widget (see Gtk2::Widget),

widget-allocation#width      integer, read-only
widget-allocation#height     integer, read-only
widget-allocation#x          integer, read-only
widget-allocation#y          integer, read-only

width and height are the widget's current size as set by its container parent (or the window manager for a top level). The values are read-only, but for example might be connected up to display somewhere,

Glib::Ex::ConnectProperties->new
  ([$toplevel, 'widget-allocation#width'],
   [$label,    'label']);

One use could be to connect the allocated size of one widget up to the width-request or height-request of another, to make it follow that size, though how closely would depend what the target's container parent might allow.

Glib::Ex::ConnectProperties->new
  ([$image,  'widget-allocation#height'],
   [$vscale, 'height-request']);

x and y are the position of the widget area within its windowed parent or grandparent, etc. These values are probably of limited use, but are included for completeness.

IMPLEMENTATION NOTES

ConnectProperties uses a notify signal handler on each object to update the others. Updating causes them to emit further notify signals (even if the value is unchanged), so some care must be taken not to have an infinite loop. The present strategy is twofold

  • An "in progress" flag in the ConnectProperties object, so during an update it recognises that any further notify emissions as its own doing and can be ignored.

  • On each target the value from a get is compared before doing a set. If already right then the set call is not made at all.

The in-progress flag acts against immediate further notifys. This could be done by temporarily disconnecting or blocking the handlers, but that seems more work than ignoring.

The compare-before-set copes with freeze_notify because in that case the notify calls don't come while the "in progress" flag is on, only later, perhaps a long time later.

If the func_in / func_out transformations are inconsistent, so that a value going in is always different from what comes out, then usually the "in progress" case prevents an infinite loop, as long as the program eventually reaches a state with no freeze_notify in force.

It might be wondered if something simpler is possible. For the general case no, not really. The specific set_foo methods on most widgets and objects often notice an unchanged setting and do nothing, but when using the generic set_property the protection above is needed.

Equality

An existing value and prospective new value are compared using values_cmp in Glib-Perl 1.220 or a fallback otherwise. For example in Glib::Param::Double anything within "epsilon" (1e-90 by default) is close enough. values_cmp lets ParamSpec subclasses control what they consider equal.

The core Glib::Param::Boxed only compares by pointer value, which is fairly useless since boxed objects are frequently copied so you probably don't have an identical pointer. ConnectProperties tries to improve this by:

  • equal or compare method from the value type when available. This covers Gtk2::Gdk::Color, Gtk2::Gdk::Region and Gtk2::TreePath.

  • Glib::Strv compared by string contents.

  • Gtk2::Border compared by field values.

  • Gtk2::Gdk::Cursor compared by type, though bitmap cursors are still only by pointer.

  • Glib::Scalar compared with eq. This may be of limited help and it's probably better to subclass Glib::Param::Scalar and make a type-specific values_cmp, if/when that's possible.

Glib::Param::Object pspecs could perhaps benefit from using an equal or compare method on the value type the same as for boxed objects. But usually when setting a Glib::Object it's a particular object which is desired, not just contents. If that's not so then as with Glib::Scalar it may be handled by a ParamSpec subclass with a values_cmp to express when different objects are equal enough (which, if/when possible, would work for both C code and Perl code comparing).

Notifies

If you're writing an object or widget (per Glib::Object::Subclass) don't forget to explicitly notify when changing a property outside a SET_PROPERTY. For example,

sub set_foo {
  my ($self, $newval) = @_;
  if ($self->{'foo'} != $newval) {
    $self->{'foo'} = $newval;
    $self->notify('foo');
  }
}

This sort of notify should be done in any object or widget implementation. Failing to do so will in particular mean ConnectProperties doesn't work, and probably other things. A SET_PROPERTY can call out to a setter function like this to re-use code. The extra notify call in that case is harmless and Glib collapses it to just one notify at the end of SET_PROPERTY.

SEE ALSO

Glib::Object, Glib::ParamSpec

HOME PAGE

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

LICENSE

Copyright 2007, 2008, 2009, 2010 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 http://www.gnu.org/licenses/.