File Coverage

blib/lib/Gtk2/Ex/ToolItem/OverflowToDialog.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # Copyright 2010, 2011, 2012 Kevin Ryde
2              
3             # This file is part of Gtk2-Ex-WidgetBits.
4             #
5             # Gtk2-Ex-WidgetBits is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU General Public License as published
7             # by the Free Software Foundation; either version 3, or (at your option) any
8             # later version.
9             #
10             # Gtk2-Ex-WidgetBits is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13             # Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Gtk2-Ex-WidgetBits. If not, see .
17              
18             package Gtk2::Ex::ToolItem::OverflowToDialog;
19 2     2   1718 use 5.008;
  2         7  
  2         86  
20 2     2   12 use strict;
  2         4  
  2         78  
21 2     2   10 use warnings;
  2         3  
  2         69  
22 2     2   1294 use Gtk2;
  0            
  0            
23             use Scalar::Util;
24             use Gtk2::Ex::ContainerBits;
25             use Gtk2::Ex::MenuBits 35; # v.35 for mnemonic_escape, mnemonic_undo
26              
27             # uncomment this to run the ### lines
28             #use Smart::Comments;
29              
30              
31             our $VERSION = 48;
32              
33             use Glib::Object::Subclass
34             'Gtk2::ToolItem',
35             interfaces => [
36             # Gtk2::Buildable new in Gtk 2.12, omit if not available
37             Gtk2::Widget->isa('Gtk2::Buildable') ? 'Gtk2::Buildable' : (),
38             ],
39             signals => { add => \&_do_add,
40             destroy => \&_do_destroy,
41             create_menu_proxy => \&_do_create_menu_proxy,
42             notify => \&_do_notify,
43             hierarchy_changed => \&_do_hierarchy_changed,
44             },
45             properties => [ Glib::ParamSpec->string
46             ('overflow-mnemonic',
47             'Overflow Mnemonic',
48             'Blurb.',
49             (eval {Glib->VERSION(1.240);1}
50             ? undef # default
51             : ''), # no undef/NULL before Perl-Glib 1.240
52             Glib::G_PARAM_READWRITE),
53              
54             Glib::ParamSpec->object
55             ('child-widget',
56             'Child Widget',
57             'Blurb.',
58             'Gtk2::Widget',
59             Glib::G_PARAM_READWRITE),
60             ];
61              
62             # sub INIT_INSTANCE {
63             # my ($self) = @_;
64             # }
65              
66             sub FINALIZE_INSTANCE {
67             my ($self) = @_;
68             ### OverflowToDialog FINALIZE_INSTANCE()
69              
70             # don't let _update_child_position() store it back again
71             delete $self->{'child_widget'};
72              
73             if (my $menuitem = delete $self->{'menuitem'}) {
74             $menuitem->destroy; # circular MenuItem<->AccelLabel, must destroy
75             }
76             if (my $dialog = delete $self->{'dialog'}) {
77             $dialog->destroy; # usual explicit Gtk2::Window destroy
78             }
79             }
80             sub _do_destroy {
81             my ($self) = @_;
82             ### OverflowToDialog _do_destroy()
83             FINALIZE_INSTANCE($self);
84             $self->signal_chain_from_overridden;
85             }
86              
87             sub SET_PROPERTY {
88             my ($self, $pspec, $newval) = @_;
89             ### ToolItem-OverflowToDialog SET_PROPERTY: $pspec->get_name
90             my $pname = $pspec->get_name;
91              
92             if ($pname eq 'child_widget') {
93             $self->set_child_widget ($newval);
94              
95             } else {
96             $self->{$pname} = $newval;
97              
98             if ($pname eq 'overflow_mnemonic') {
99             # propagate
100             if (my $menuitem = $self->{'menuitem'}) {
101             if (my $label = $menuitem->get_child) { # perhaps gone in destruction
102             $label->set_text_with_mnemonic (_mnemonic_text ($self));
103             }
104             }
105             if (my $dialog = $self->{'dialog'}) {
106             $dialog->update_text;
107             }
108             }
109             }
110             }
111             sub _do_notify {
112             my ($self, $pspec) = @_;
113             ### ToolItem-OverflowToDialog _do_notify(): $pspec->get_name
114             ### invocation hint: $self->signal_get_invocation_hint
115              
116             # my $n;
117             # if ($n++ > 10) {
118             # exit 1;
119             # }
120             # if (my $child = $self->get('child-widget')) {
121             # if ($child->isa('Gtk2::Container')) {
122             # ### child-widget: join(',', $child->get_children)
123             # }
124             # }
125              
126             $self->signal_chain_from_overridden ($pspec);
127             ### chain returned ...
128              
129             # The GtkToolItem gtk_tool_item_property_notify() propagates 'sensitive'
130             # to the menuitem already, whatever is currently set_proxy_menu_item().
131             # Send it to the dialog too.
132             my $pname = $pspec->get_name;
133             if ($pname eq 'sensitive' || $pname eq 'tooltip_text') {
134             my $newval = $self->get($pname);
135             foreach my $target ($self->{'menuitem'},
136             $self->{'dialog'} && $self->{'dialog'}->{'child_vbox'}) {
137             if ($target) {
138             ### propagate: "$pname to $target"
139             $target->set ($pname => $newval);
140             }
141             }
142             }
143             }
144              
145             # 'add' class closure, per $container->add()
146             sub _do_add {
147             my ($self, $child) = @_;
148             ### ToolItem-OverflowToDialog _do_add(): "$child"
149              
150             $self->signal_chain_from_overridden ($child);
151             $self->set_child_widget ($child);
152             }
153              
154             # not documented yet
155             sub set_child_widget {
156             my ($self, $child_widget) = @_;
157              
158             # watch out for recursion from _do_add()
159             if ((Scalar::Util::refaddr($self->{'child_widget'})||0)
160             == (Scalar::Util::refaddr($child_widget)||0)) {
161             ### unchanged
162             return;
163             }
164              
165             $self->{'child_widget'} = $child_widget;
166             _update_child_position ($self);
167              
168             # go through an idle handler as a workaround for some dodginess in glib
169             # circa 2.30 running a notify closure from within an add closure ...
170             Glib::Idle->add (sub {
171             $self->notify('child_widget');
172             return 0; # Glib::SOURCE_REMOVE;
173             });
174             }
175              
176             sub _update_child_position {
177             my ($self) = @_;
178             my $child_widget = $self->{'child_widget'};
179             if (my $dialog = $self->{'dialog'}) {
180             my $child_vbox = $dialog->{'child_vbox'};
181             if ($dialog->mapped) {
182             # want $child_widget in the dialog
183             Gtk2::Ex::ContainerBits::remove_all ($self);
184             foreach my $old ($child_vbox->get_children) {
185             if ((Scalar::Util::refaddr($old)||0)
186             == (Scalar::Util::refaddr($child_widget)||0)) {
187             # already in the dialog, don't pack
188             undef $child_widget;
189             } else {
190             $child_vbox->remove ($old);
191             }
192             }
193             if ($child_widget) {
194             # expand/fill with dialog
195             $child_vbox->pack_start ($child_widget, 1,1,0);
196             }
197             return;
198             }
199              
200             # want $child_widget in the toolitem
201             Gtk2::Ex::ContainerBits::remove_all ($child_vbox);
202             }
203             _bin_set_child ($self, $child_widget);
204             }
205              
206             # Gtk2::Ex::BinBits::set_child ($bin, $child)
207             #
208             # Set the child widget in C<$bin> to C<$child>. This is done by a C
209             # of any existing child and an C of C<$child>. C<$child> can be undef
210             # to set no child.
211             #
212             # When making a subclass of C this function can be imported to
213             # have it available as a method on the new class, if desired.
214             #
215             sub _bin_set_child {
216             my ($bin, $child) = @_;
217             if (my $old_child = $bin->get_child) {
218             if ((Scalar::Util::refaddr($child)||0) == (Scalar::Util::refaddr($old_child)||0)) {
219             return;
220             }
221             $bin->remove ($old_child);
222             }
223             if (defined $child) {
224             if (my $old_parent = $child->get_parent) {
225             $old_parent->remove ($child);
226             }
227             $bin->add ($child);
228             }
229             }
230              
231             # # Gtk2::Ex::ContainerBits::set_children ($container, $child, ...)
232             # #
233             # # Set the children of C<$container> to the given C<$child...> arguments.
234             # # For convenience any Cs in the arguments are ignored.
235             # #
236             # # This is done by a C of any existing children which are not listed,
237             # # and an add C of any new additional children given. If a C<$child> if
238             # # in C<$container> but not in the position given by the arguments then it's
239             # # removed and re-added.
240             # #
241             # # when making a subclass of C this function can be imported
242             # # to have it available as a method on the new class, if desired.
243             # #
244             # sub _container_set_children {
245             # my $container = shift;
246             # @_ = grep {defined} @_;
247             # my @remove;
248             # my @children = $container->get_children;
249             # while (@children) {
250             # my $old_child = shift @children;
251             # if ((Scalar::Util::refaddr($_[0])||0)
252             # == (Scalar::Util::refaddr($children[0])||0)) {
253             # shift @_;
254             # } else {
255             # push @remove, $old_child;
256             # }
257             # }
258             # Gtk2::Ex::ContainerBits::remove_widgets ($container, @remove);
259             # foreach my $add (@_) {
260             # $container->add ($add);
261             # }
262             # }
263              
264             # 'hierarchy-changed' class closure handler
265             sub _do_hierarchy_changed {
266             my ($self, $pspec) = @_;
267             ### ToolItem-OverflowToDialog _do_hierarchy_changed()
268              
269             # cf ConnectProperties self#toplevel -> dialog#transient-for
270             # except transient-for prop new in 2.10
271             if (my $dialog = $self->{'dialog'}) {
272             $dialog->update_transient_for;
273             }
274             }
275              
276             sub _do_create_menu_proxy {
277             my ($self) = @_;
278             ### ToolItem-OverflowToDialog _do_create_menu_proxy()
279             ### visible: $self->get('visible')
280              
281             $self->{'menuitem'} ||= do {
282             # initial and subsequent sensitivity propagated by GtkToolItem
283             my $menuitem = Gtk2::MenuItem->new_with_mnemonic (_mnemonic_text($self));
284             if ($self->find_property('tooltip-text')) { # new in Gtk 2.12
285             $menuitem->set_property (tooltip_text => $self->get('tooltip-text'));
286             }
287             # or ConnectProperties ...
288             Scalar::Util::weaken (my $weak_self = $self);
289             $menuitem->signal_connect (activate => \&_do_menu_activate, \$weak_self);
290             $menuitem
291             };
292              
293             $self->set_proxy_menu_item (__PACKAGE__, $self->{'menuitem'});
294             return 1;
295             }
296              
297             sub _do_menu_activate {
298             my ($menuitem, $ref_weak_self) = @_;
299             my $self = $$ref_weak_self || return;
300             ### ToolItem-OverflowToDialog _do_menu_activate()
301             _dialog($self)->present_for_menuitem ($menuitem);
302             }
303             sub _dialog {
304             my ($self) = @_;
305             return ($self->{'dialog'} || do {
306             ### create new dialog
307             require Gtk2::Ex::ToolItem::OverflowToDialog::Dialog;
308             my $d = $self->{'dialog'}
309             = Gtk2::Ex::ToolItem::OverflowToDialog::Dialog->new (toolitem => $self);
310             _do_hierarchy_changed ($self); # initial transient_for
311             $d
312             });
313             }
314              
315             sub _mnemonic_text {
316             my ($self) = @_;
317             my $str = $self->{'overflow_mnemonic'};
318             if (defined $str) {
319             return $str;
320             } elsif (my $child_widget = $self->{'child_widget'}) {
321             return Gtk2::Ex::MenuBits::mnemonic_escape ($child_widget->get_name);
322             } else {
323             return '';
324             }
325             }
326              
327             #------------------------------------------------------------------------------
328             # Gtk2::Buildable interface
329              
330             sub GET_INTERNAL_CHILD {
331             my ($self, $builder, $name) = @_;
332             if ($name eq 'overflow_menuitem') {
333             $self->signal_emit ('create-menu-proxy');
334             return $self->retrieve_proxy_menu_item;
335             }
336             if ($name eq 'dialog') {
337             return _dialog($self);
338             }
339             # ENHANCE-ME: Will Gtk2::Buildable wrapping expect anything to chain up?
340             return undef;
341             }
342              
343             1;
344             __END__