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::Object
s (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.
+--------------------+ +-------------+
| 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. See examples/sensitive.pl in the Glib-Ex-ConnectProperties sources for a complete program.
Property Types
The following property types are supported
string
number integer or float
enum Glib::Enum subtypes
flags Glib::Flags subtypes
object Glib::Object
string array Glib::Strv
some boxed Glib::Boxed
Boxed types which work include Gtk2::Gdk::Color
and Gtk2::Gdk::Rectangle
, but others may not. See "Equality" below.
Read-only properties on objects can be used. They're propagated out to the other linked properties but changes in those others are not stored back. Usually this is only useful when the read-only is the only one changing. You could easily enough make an explicit signal handler to propagate the value, but a ConnectProperties is convenient and is careful not to make circular references. See the read_only
option below to force read-only.
Write-only properties can be used. Nothing is read out of them, they're just set from changes in the other linked properties. Sometimes write-only properties are pseudo "add" methods etc, so a write-only would be unusal. See the write_only
option below to force write-only.
It works to link properties on the same object. This can ensure they update together. It also works to have different ConnectProperties linkages with an object/property in common. A change coming from one group propagates through to the other. This arises quite naturally if there's two controls for the same target.
A property name can include an explicit class like GtkLabel::justify
in the usual style of set_property()
, find_property()
, etc.
[ $widget, 'GtkLabel::justify' ]
If a subclass accidentally shadows a superclass property name then this gives access to the superclass property. But it's otherwise unnecessary and not recommended. For a Perl subclass like My::Foo::Bar
the fully-qualified name 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 withdisconnect()
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 constructors are arrayrefs with an object, a property name, and perhaps further options as described below. For example
Glib::Ex::ConnectProperties->new
([$object1, 'some-propname'],
[$object2, 'another-propname']);
An initial value is propagated from the first object+property (the first readable one) to set all the others if they're not already the same. So 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 desired linkages might change and you want to drop an old one and make a new. For example something like the following inside a widget or object would allow a target to be changed, including changed to undef
for nothing linked.
sub set_target {
my ($self, $target_object) = @_;
$self->{'conn'} =
$target_object
&& Glib::Ex::ConnectProperties->dynamic
([$self, 'my-prop'],
[$target, 'target-prop']);
}
Operations
$conn->disconnect()
-
Disconnect the given ConnectProperties linkage.
$conn
can made by eithernew()
anddynamic()
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 can 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 of code.
read_signal => $signame
-
Connect to
$signame
to see changes to the property. The default is the notify signal asnotify::$propname
which 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
thetext
property notifies for every character typed by the user. Using theactivate
signal instead you can 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 isundef
.Generally the signals which are useful for triggering a read have no return value (ie.
void
), so no particular return is needed. But for example if a widget event handler was a good time to look at a property then a return ofGtk2::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
Storing a value goes through the following steps,
Value transformations specified in the element, if any.
value_validate()
of the target ParamSpec (in Glib-Perl 1.220 where that method is available).Equality check, if the target is readable, to avoid a
set_property()
if it's already what's desired (see "Equality" below).set_property()
value_validate()
does things like clamp numbers outside the ParamSpec min/max, perhaps manipulate string contents, etc. This at least gives something which can be stored.
Perl's usual value coercing such as stringizing, numizing, truncating integers, etc, applies to the value_validate()
call and the set_property()
call, in the usual way. This means string properties can be linked to number properties or similar with no special tranformations.
In the following options the "in" transformations are for storing and the "out" for reading. func
is the most general. hash
is handy for a fixed set of possible values.
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. (See examples/func-transform.pl in the Glib-Ex-ConnectProperties sources for a complete program doing this.) 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 beundef
in the usual way. Various tied hash modules can change that in creative ways, for exampleHash::WithDefaults
to look in fallback hashes.The hashes are not copied, so future changes to their contents will be used, though there's nothing to forcibly update property values if current settings might be affected.
See examples/hash-transform.pl in the Glib-Ex-ConnectProperties sources for a complete program using hash transforms.
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 and a write-only property only needs an "in" transformation, including when the read_only
or write_only
options above force it ("General Options").
OTHER SETTINGS
Various additional object or widget settings can be accessed by ConnectProperties. They're either different flavour properties, or are non-property attributes which have some sort of signal notifying when they change. They're grouped as follows and described in detail below.
child# Gtk2::Container child properties
combobox-active# Gtk2::ComboBox active item
model-rows# Gtk2::TreeModel rows
response-sensitive# Gtk2::Dialog and Gtk2::InfoBar
screen-size# Gtk2::Gdk::Screen width,height
tree-selection# Gtk2::TreeSelection active
iconview-selection# Gtk2::IconView active
widget# Gtk2::Widget various
widget-allocation# Gtk2::Widget width,height,x,y
The Gtk2
things don't create a dependency on Gtk2
unless you use them. The implementation is modular so the extras are not loaded unless used. The #
separator character doesn't clash with plain property names as it's not allowed in a ParamSpec name.
Container Child Properties
Gtk2::Container
subclasses can define "child properties" which exist on a widget when it's in that type of container. For example Gtk2::Table
has child properties for the attach positions. These are separate from normal object properties.
Child properties can be accessed from ConnectProperties in Perl-Gtk2 1.240 and up (where find_child_property()
is available). The property names are "child#top-attach" etc on the child widget.
Glib::Ex::ConnectProperties->new
([$adj, 'value'],
[$childwidget, 'child#bottom-attach']);
$childwidget
should be in a container which has the given child property. If unparented later then nothing is read or written. Unparenting happens during destruction and quietly doing nothing is usually what you want.
It's unspecified yet what happens if $childwidget
is reparented. In the current code Gtk emits a child-notify
for each property so the initial value from the container propagates out. It may be better to apply the first readable ConnectProperties element onto the child, like a ConnectProperties creation. But noticing a reparent requires a parent-set
or notify::parent
signal, so perhaps a watch_reparent
option should say when reparent handling might be needed, so as not to listen for something which won't happen.
ComboBox Active Row
The active row of a Gtk2::ComboBox
can be accessed and controlled from ConnectProperties with the following. The Gtk2::Ex::ComboBoxBits
helper module is required.
combobox-active#exists boolean, read-only
combobox-active#path Gtk2::TreePath
combobox-active#iter Gtk2::TreeIter
combobox-active#text string
path
and iter
are good for active sub-rows. The plain ComboBox active
is enough for just toplevel row.
text
is for use on a "simplified text" ComboBox as created by Gtk2::ComboBox->new_text()
.
Response Sensitive
Response sensitivity on a Gtk2::Dialog
, Gtk2::InfoBar
or similar can be controlled from ConnectProperties with
response-sensitive#ok boolean
response-sensitive#123 boolean
The name part after the "#" is a Gtk2::ResponseType
nick or name, or an integer application-defined response code (usually a positive integer).
Glib::Ex::ConnectProperties->new
([$job, 'have-help-available'],
[$dialog, 'response-sensitive#help', write_only => 1]);
response-sensitive
is always writable, applied with set_response_sensitive()
. Often writing is all that's needed and the write_only
option can force that if desired (see "General Options").
response-sensitive
is readable if the widget has a get_response_for_widget()
method, which means Gtk 2.8 up for Dialog, but not available for InfoBar (as of Gtk 2.22). There must be at least one button etc using the response, since sensitivity is not recorded in the dialog, it only sets the sensitive
property of action area widgets. ConnectProperties currently assumes the first widget it finds using the response will not be removed. Perhaps this will be relaxed in the future, but perhaps only as an option since buttons are normally unchanging and extra listening would be needed to notice a change.
Button sensitivity can instead be controlled directly by finding the widget (or perhaps multiple widgets) for the given response and setting their sensitive
property. This response-sensitive#
lets someone else do the widget lookups.
Screen Size
Gtk2::Gdk::Screen
width and height in pixels or millimetres can be accessed with
screen-size#width integer pixels, read-only
screen-size#height integer pixels, read-only
screen-size#width-mm integer millimetres, read-only
screen-size#height-mm integer millimetres, read-only
These are $screen->get_width()
etc. width
and height
changes are from the size-changed
signal, and in Gtk 2.14 width-mm
and height-mm
changes are from the monitors-changed
signal (before 2.14 the millimetres don't change).
The size in pixels can change with the video mode, and a size in millimetres change can occur from a RANDR or Xinerama rearrangement of output monitors. In all cases the sizes are read-only, since Gtk2::Gdk::Screen
doesn't have anything to perform video mode or monitor changes.
For example to display the width in a label,
my $toplevel = Gtk2::Window->new('toplevel');
my $screen = $toplevel->get_screen;
# to display the size in some label widget
Glib::Ex::ConnectProperties->new
([$screen, 'screen-size#width'],
[$label, 'label']);
For reference, under X the fullscreen()
mode in Gtk2::Gdk::Window
probably depends on the window manager noticing screen size changes. Hopefully it's not necessary for an application to link screen-size#
to the window size to keep full screen on screen size changes.
TextBuffer Contents
The size of the text in a Gtk2::TextBuffer
object can be accessed with
textbuffer#empty boolean, read-only
textbuffer#not-empty boolean, read-only
textbuffer#char-count integer, read-only
For example "not-empty" might be connected up to make a clear button sensitive only when there is in fact something to clear
my $treeselection = $treeview->get_selection;
Glib::Ex::ConnectProperties->new
([$textbuf, 'textbuffer#not-empty'],
[$button, 'sensitive', write_only => 1]);
These attributes use $textbuf->get_char_count()
. Gtk2::TextBuffer
doesn't offer this count from a property as such, only a method.
The full content is available as text
property, but Gtk circa 2.24.8 doesn't seem to emit a notify
for it, so if linking that use read_signal => "changed"
.
Tree Model Rows
The existence of rows in a Gtk2::TreeModel
can be accessed with
model-rows#empty boolean, read-only
model-rows#not-empty boolean, read-only
These are read-only but might for instance be used to make a control widget sensitive only when a model has rows to act on.
Glib::Ex::ConnectProperties->new
([$model, 'model-rows#not-empty'],
[$button, 'sensitive']);
Emptiness is simply per get_iter_first()
. The row-deleted
or row-inserted
signals are used to listen for becoming empty or not empty.
TreeView and IconView Selected Rows
Row selection in a Gtk2::TreeSelection
object (as used by Gtk2::TreeView
) or in a Gtk2::IconView
can be accessed with
tree-selection#empty boolean, read-only
tree-selection#not-empty boolean, read-only
tree-selection#count integer, read-only
tree-selection#selected-path Gtk2::TreePath or undef
iconview-selection#empty boolean, read-only
iconview-selection#not-empty boolean, read-only
iconview-selection#count integer, read-only
iconview-selection#selected-path Gtk2::TreePath or undef
For example "not-empty" might be connected up to make a delete button sensitive only when the user selects a row,
my $treeselection = $treeview->get_selection;
Glib::Ex::ConnectProperties->new
([$treeselection, 'tree-selection#not-empty'],
[$button, 'sensitive', write_only => 1]);
selected-path
is the first selected row and is intended for use with "single" selection mode where there's at most one row selected and a select_path()
switches from any existing selected row to just the new one. It might be used to synchronise the selected item in two TreeViews.
Rows in a TreeSelection and items in an IconView are similar but not quite the same and so are kept as separate tree-selection#
and icon-selection#
.
For reference a "selected-iter" of type Gtk2::TreeIter
might mostly work, though would prefer an equal()
or compare()
in the type rather than going via the model (Perl-Gtk2's to_arrayref()
access only suits Perl code models).
Widget Various
The following various widget attributes can be accessed from ConnectProperties.
widget#direction Gtk2::TextDirection enum, ltr or rtl
widget#screen Gtk2::Gdk::Screen
widget#has-screen boolean, read-only
widget#state Gtk2::StateType enum
widget#toplevel Gtk2::Window or undef, read-only
These things aren't properties (though perhaps they could have been) but instead have get/set methods and report changes with specific signals.
widget#direction
is the "ltr" or "rtl" text direction, perget_direction()
andset_direction()
methods.If "none" is set then
get_direction()
gives back "ltr" or "rtl" following the global default. Storing "none" with ConnectProperties probably won't work very well, except to a forcedwrite_only
target so that it's not read back.widget#screen
uses theget_screen()
method and so gives the default screen until the widget is added to a toplevelGtk2::Window
or similar to determine the screen.widget#screen
is read-only for most widgets, but is writable for anything with aset_screen()
such asGtk2::Menu
. There's a plainscreen
property onGtk2::Window
so it doesn't need this specialwidget#screen
, but other widgets benefit.Gtk2::Gdk::Screen
is new in Gtk 2.2 andwidget#screen
andwidget#has-screen
are not available in Gtk 2.0.x.widget#state
is thestate()
/set_state()
condition, such as "normal" or "prelight".Note that storing "insensitive" doesn't work very well, since a subsequent setting back to "normal" doesn't turn the sensitive flag back on. Perhaps this will change in the future, so as to actually enforce the desired new state.
widget#toplevel
is an ancestor withtoplevel
flag set, orundef
if none. This isget_toplevel
and its recommended$parent->toplevel
flag check (as notified byhierarchy-changed
).Glib::Ex::ConnectProperties->new ([$toolitem, 'widget#toplevel'], [$dialog, 'transient-for']);
The toplevel is normally a
Gtk2::Window
or subclass but in principle could be another class.
Widget Allocation
$widget->allocation
fields on a Gtk2::Widget
(see Gtk2::Widget) can be read with
widget-allocation#width integer, read-only
widget-allocation#height integer, read-only
widget-allocation#x integer, read-only
widget-allocation#y integer, read-only
widget-allocation#rectangle Gtk2::Gdk::Rectangle, 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']);
A possible use might be to connect the allocated size of one widget to the width-request
or height-request
of another so as to make it follow that size, though how closely depends on what the target's container parent might allow. (See Gtk2::SizeGroup
for inducing a common width or height request among a set of widgets.)
Glib::Ex::ConnectProperties->new
([$image, 'widget-allocation#height'],
[$vscale, 'height-request']);
x
and y
are the position of the widget area within its windowed ancestor. rectangle
is the whole $widget->allocation()
object. These may be of limited use but are included for completeness.
IMPLEMENTATION NOTES
ConnectProperties uses a notify
signal handler on each object to update the others. Updating those others causes them to emit their own 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 aset
. If already right then theset
call is not made at all.
The in-progress flag acts against immediate further notify
s. This could also be done by temporarily disconnecting or blocking the handlers, but that seems more work than ignoring.
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()
orcompare()
method from the value type when available. This coversGtk2::Gdk::Color
,Gtk2::Gdk::Region
andGtk2::TreePath
.Glib::Strv
compared by string contents.Gtk2::Border
compared by field values.Gtk2::Gdk::Cursor
compared bytype
, though bitmap cursors are still only by pointer.Glib::Scalar
compared witheq
. This may be of limited help and it's probably better to subclassGlib::Param::Scalar
and make a type-specificvalues_cmp
, if/when that's possible.
Glib::Param::Object
pspecs could perhaps benefit from using an equal()
or compare()
method on the object 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 could be handled by a ParamSpec subclass with a values_cmp()
to express when different objects are equal enough. If/when possible that would work for both C code and Perl code comparisons.
Object Implementation
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
is necessary 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 the above to re-use code. In that case Glib collapses the notify
to just one notify signal at the end of SET_PROPERTY()
.
SEE ALSO
HOME PAGE
http://user42.tuxfamily.org/glib-ex-connectproperties/index.html
LICENSE
Copyright 2007, 2008, 2009, 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 http://www.gnu.org/licenses/.