File Coverage

blib/lib/Gtk2/Ex/WidgetCursor.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             # Copyright 2007, 2008, 2009, 2010 Kevin Ryde
2              
3             # This file is part of Gtk2-Ex-WidgetCursor.
4             #
5             # Gtk2-Ex-WidgetCursor 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-WidgetCursor 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-WidgetCursor. If not, see .
17              
18             package Gtk2::Ex::WidgetCursor;
19 4     4   74592 use 5.006;
  4         14  
  4         162  
20 4     4   19 use strict;
  4         6  
  4         102  
21 4     4   18 use warnings;
  4         9  
  4         99  
22 4     4   17 use Carp;
  4         7  
  4         325  
23 4     4   6280 use Gtk2;
  0            
  0            
24             use List::Util;
25             use POSIX ();
26             use Scalar::Util 1.18; # 1.18 for pure-perl refaddr() fix
27              
28             # uncomment this to run the ### lines
29             #use Smart::Comments;
30              
31             our $VERSION = 15;
32              
33             # Gtk 2.2 for get_display()
34             # could work without it, but probably not worth bothering
35             Gtk2->CHECK_VERSION(2,2,0)
36             or die "WidgetCursor requires Gtk 2.2 or higher";
37              
38              
39             #------------------------------------------------------------------------------
40             # Cribs on widgets using gdk_window_set_cursor directly:
41             #
42             # GtkAboutDialog [not handled]
43             # Puts "email" and "link" tags on text in the credits GtkTextView and
44             # then does set_cursor on entering or leaving those.
45             #
46             # GtkCombo [ok mostly, with a hack]
47             # Does a single set_cursor for a 'top-left-arrow' on a GtkEventBox in
48             # its popup when realized. We dig that out for include_children,
49             # primarily so a busy() shows the watch on the popup window if it
50             # happens to be open. Of course GtkCombo is one of the ever-lengthening
51             # parade of working and well-defined widgets which Gtk says you're not
52             # meant to use any more.
53             #
54             # GtkCurve [not handled]
55             # Multiple set_cursor calls according to mode and motion. A rarely used
56             # widget so ignore it for now.
57             #
58             # GtkEntry [ok, with a hack]
59             # An Entry uses a private GdkWindow subwindow 4 pixels smaller than the
60             # main and sets a GDK_CURSOR_XTERM there when sensitive. That window
61             # isn't presented in the public fields/functions but can be dug out from
62             # $win->get_children. We set the cursor on both the main window and the
63             # subwindow then have a hack to restore the insertion point cursor on
64             # the latter when done. Getting the subwindow is fast since Gtk
65             # maintains the list of children for gdk_window_get_children() itself
66             # (as opposed to the way plain Xlib queries the server).
67             #
68             # The Entry can be made to restore the insertion cursor by toggling
69             # 'sensitive'. Hard to know which is worse: toggling sensitive
70             # needlessly, or forcibly setting the cursor back. The latter is needed
71             # for the SpinButton subclass below, so it's easier to do that.
72             #
73             # GtkFileChooser [probably ok]
74             # Sets a GDK_CURSOR_WATCH temporarily when busy. That probably kills
75             # any WidgetCursor setting, but probably GtkFileChooser isn't something
76             # you'll manipulate externally.
77             #
78             # GtkLabel [not handled]
79             # Puts GDK_XTERM on a private selection window when sensitive and
80             # selectable text, or something. This misses out on include_children
81             # for now.
82             #
83             # GtkLinkButton [not very good]
84             # A GtkButton subclass which does 'hand' set_cursor on its windowed
85             # parent for enter and leave events on its input-only event window.
86             #
87             # The cursor applied to the event window (per GtkButton above) trumps
88             # the hand on the parent, so that gets the right effect. But any
89             # WidgetCursor setting on the parent is lost when LinkButton turns off
90             # its hand under a leave-event. Might have to make a hack connecting to
91             # leave-event and re-applying the parent window.
92             #
93             # GtkPaned [not handled]
94             # Puts a cursor on its GdkWindow handle when sensitive. Not covered by
95             # include_children for now.
96             #
97             # GtkRecentChooser [probably ok]
98             # A GDK_WATCH when busy, similar to GtkFileChooser above. Hopefully ok
99             # most of the time with no special attention.
100             #
101             # GtkSpinButton [imperfect]
102             # Subclass of GtkEntry, but adds a "panel" window of arrows. In Gtk
103             # 2.12 it was overlaid on the normal Entry widget window, ie. the main
104             # outer one. In Gtk 2.14 it's a child of that outer window.
105             #
106             # For 2.12 it can be dug out by looking for sibling windows with events
107             # directed to the widget. Then it's a case of operating on three
108             # windows: the Entry main, the Entry 4-pixel smaller subwindow and the
109             # SpinButton panel.
110             #
111             # As of Gtk 2.12 toggling sensitive doesn't work to restore the
112             # insertion point cursor for a SpinButton, unlike its Entry superclass.
113             # Something not chaining up presumably, so the only choice is to
114             # forcibly put the cursor back.
115             #
116             # GtkStatusBar [not handled]
117             # A cursor on its private grip GdkWindow.
118             #
119             # GtkTextView [ok]
120             # Sets a GDK_XTERM insertion point cursor on its get_window('text')
121             # sub-window when sensitive. We operate on the get_window('widget') and
122             # get_window('text') both.
123             #
124             # Toggling sensitive will put back the insertion point cursor, like for
125             # a GtkEntry above, and like for the Entry it's hard to know whether
126             # it's worse to toggle sensitive or forcibly set back the cursor. For
127             # now the latter can share code with Entry and SpinButton and thus is
128             # what's used.
129             #
130              
131              
132             #------------------------------------------------------------------------------
133              
134             use Glib::Object::Subclass
135             'Glib::Object',
136             properties => [ Glib::ParamSpec->object
137             ('widget',
138             'widget',
139             'The widget to show the cursor in, if just one widget.',
140             'Gtk2::Widget',
141             Glib::G_PARAM_READWRITE),
142              
143             Glib::ParamSpec->scalar
144             ('widgets',
145             'widgets',
146             'An arrayref of widgets to show the cursor in.',
147             Glib::G_PARAM_READWRITE),
148              
149             Glib::ParamSpec->object
150             ('add-widget',
151             'add-widget',
152             'Pseudo-property to add a widget to the cursor in.',
153             'Gtk2::Widget',
154             ['writable']),
155              
156             Glib::ParamSpec->scalar
157             ('cursor',
158             'cursor',
159             'Cursor to use while dragging, as any name or object accepted by Gtk2::Ex::WidgetCursor.',
160             Glib::G_PARAM_READWRITE),
161             #
162             # when glib 1.240 has fix for this pspec/get/set style
163             # {
164             # pspec => ...,
165             # get => \&cursor,
166             # set => \&cursor,
167             # }
168              
169             Glib::ParamSpec->string
170             ('cursor-name',
171             'cursor-name',
172             'Cursor to use while dragging, as cursor type enum nick plus "invisible".',
173             '', # FIXME: default is undef when gtk2-perl allows that
174             Glib::G_PARAM_READWRITE),
175              
176             Glib::ParamSpec->boxed
177             ('cursor-object',
178             'cursor-object',
179             'Cursor to use while dragging, as cursor object.',
180             'Gtk2::Gdk::Cursor',
181             Glib::G_PARAM_READWRITE),
182              
183             Glib::ParamSpec->boolean
184             ('active',
185             'active',
186             'Whether to show this cursor.',
187             0, # default no
188             Glib::G_PARAM_READWRITE),
189             # when glib 1.240 has fix for this pspec/get/set style
190             # {
191             # pspec => '',
192             # get => \&active,
193             # set => \&active,
194             # }
195              
196             Glib::ParamSpec->double
197             ('priority',
198             'priority',
199             'The priority of this cursor among multiple WidgetCursors on a given widget. Higher numbers are higher priority.',
200             - POSIX::DBL_MAX(), # min
201             POSIX::DBL_MAX(), # max
202             0, # default
203             Glib::G_PARAM_READWRITE),
204              
205             Glib::ParamSpec->boolean
206             ('include-children',
207             'include-children',
208             'Whether to apply the cursor to child widgets too.',
209             0, # default no
210             Glib::G_PARAM_READWRITE),
211              
212             ];
213              
214             # @wobjs is all the WidgetCursor objects which currently exist, sorted from
215             # highest to lowest priority, and from newest to oldest among those of equal
216             # priority
217             #
218             # Elements are weakened so they don't keep the objects alive. The DESTROY
219             # method strips elements and undefs from here, but not sure if undef could
220             # still be seen in here by certain funcs at certain times.
221             #
222             my @wobjs = ();
223              
224             sub INIT_INSTANCE {
225             my ($self) = @_;
226             $self->{'installed_widgets'} = [];
227             _wobjs_insert ($self);
228             }
229             sub FINALIZE_INSTANCE {
230             my ($self) = @_;
231             ### FINALIZE_INSTANCE: "$self"
232             _splice_out (\@wobjs, $self);
233             if (delete $self->{'active'}) {
234             _wobj_deactivated ($self);
235             }
236             }
237              
238             sub GET_PROPERTY {
239             my ($self, $pspec) = @_;
240             ### WidgetCursor GET_PROPERTY(): $pspec->get_name
241             my $pname = $pspec->get_name;
242              
243             if ($pname eq 'cursor_name') {
244             my $cursor = $self->{'cursor'};
245             if (Scalar::Util::blessed($cursor)) {
246             $cursor = $cursor->type;
247             }
248             return $cursor;
249             }
250             if ($pname eq 'cursor_object') {
251             my $cursor = $self->{'cursor'};
252             return (Scalar::Util::blessed($cursor)
253             && $cursor->isa('Gtk2::Gdk::Cursor')
254             && $cursor);
255             }
256              
257             return $self->{$pname};
258             }
259              
260             sub SET_PROPERTY {
261             my ($self, $pspec, $newval) = @_;
262             ### WidgetCursor SET_PROPERTY(): $pspec->get_name
263             my $pname = $pspec->get_name;
264              
265             if ($pname eq 'active') {
266             $self->active($newval);
267             return;
268             }
269             if ($pname =~ /^cursor/) {
270             $self->cursor($newval);
271             return;
272             }
273             if ($pname eq 'add_widget') {
274             $self->add_widgets ($newval);
275             return;
276             }
277              
278             if ($pname eq 'widget') {
279             $pname = 'widgets';
280             $newval = [ $newval ];
281             }
282             if ($pname eq 'widgets') {
283             my @array;
284             push @array, @$newval;
285             foreach (@array) { Scalar::Util::weaken ($_); }
286             $self->{'widgets'} = \@array;
287             if ($self->{'active'}) {
288             _wobj_activated ($self);
289             }
290             return;
291             }
292              
293             $self->{$pname} = $newval;
294              
295             if ($pname eq 'priority') {
296             _wobjs_insert ($self);
297             if ($self->{'active'}) {
298             _wobj_activated ($self);
299             }
300             }
301             }
302              
303             # insert $self into @wobjs according to $self->{'priority'}
304             sub _wobjs_insert {
305             my ($self) = @_;
306              
307             my $priority = ($self->{'priority'}||0);
308             my $pos = 0;
309             while ($pos < @wobjs) {
310             # FINALIZE_INSTANCE removes on destroy, but beware of undef just in case
311             if (my $wobj = $wobjs[$pos]) {
312             if ($wobj == $self) {
313             splice @wobjs,$pos,1; # remove from old position
314             next;
315             }
316             if ($priority >= ($wobj->{'priority'}||0)) {
317             last;
318             }
319             }
320             $pos++;
321             }
322             splice @wobjs,$pos,0, $self;
323             Scalar::Util::weaken ($wobjs[$pos]);
324             }
325              
326             # get or set "active"
327             sub active {
328             my ($self, $newval) = @_;
329             if (@_ < 2) { return $self->{'active'}; } # get
330              
331             # set
332             $newval = ($newval ? 1 : 0); # don't capture arbitrary input
333             my $oldval = $self->{'active'};
334             $self->{'active'} = $newval;
335             if ($oldval && ! $newval) {
336             _wobj_deactivated ($self);
337             $self->notify('active');
338             } elsif ($newval && ! $oldval) {
339             _wobj_activated ($self);
340             $self->notify('active');
341             }
342             }
343              
344             # newly turned off or destroyed
345             sub _wobj_deactivated {
346             my ($self) = @_;
347             ### _wobj_deactivated()
348             my $aref = $self->{'installed_widgets'};
349             ### $aref
350             $self->{'installed_widgets'} = [];
351             foreach my $widget (@$aref) {
352             _update_widget ($widget);
353             }
354             }
355              
356             # newly turned on or created on
357             sub _wobj_activated {
358             my ($self) = @_;
359             ### _wobj_activated()
360              
361             if ($self->{'include_children'}) {
362             # go through widgets in other wobjs as well as ourselves since they may
363             # be affected if they're children of one of ours (%done skips duplicates
364             # among the lists)
365             ### include_children other wobjs
366             my %done;
367             foreach my $wobj (@wobjs) {
368             foreach my $widget (@{$wobj->{'widgets'}}) {
369             if (! $widget) { next; } # possible undef by weakening
370             $done{Scalar::Util::refaddr($widget)}
371             ||= do { _update_widget ($widget); 1 };
372             }
373             }
374              
375             # special handling for children of certain types that might be present
376             # deep in the tree
377             ### include_children special TextView etc
378             foreach my $widget (@{$self->{'widgets'}}) {
379             if (! $widget) { next; } # possible undef by weakening
380              
381             foreach my $widget (_container_recursively ($widget)) {
382             if ($widget->isa('Gtk2::Entry')
383             || $widget->isa('Gtk2::TextView')
384             || _widget_is_combo_eventbox ($widget)) {
385             $done{Scalar::Util::refaddr($widget)}
386             ||= do { _update_widget ($widget); 1 };
387             }
388             }
389             }
390              
391             } else {
392             # simple non-include-children for this wobj, look only at its immediate
393             # widgets
394             foreach my $widget (@{$self->{'widgets'}}) {
395             _update_widget ($widget);
396             }
397             }
398             }
399              
400             sub _update_widget {
401             my ($widget) = @_;
402             if (! $widget) { return; } # possible undef from weakening
403             ### _update_widget: "$widget", $widget->get_name
404              
405             # find wobj with priority on this $widget
406             my $wobj = List::Util::first
407             { $_->{'active'} && _wobj_applies_to_widget($_,$widget)} @wobjs;
408              
409             my $old_wobj = $widget->{__PACKAGE__.'.installed'};
410             ### wobj was: defined $old_wobj && $old_wobj->{'cursor'}
411             ### now: defined $wobj && $wobj->{'cursor'}
412             ### window: "@{[$widget->window||'undef']}"
413              
414             if (($wobj||0) == ($old_wobj||0)) { return; } # unchanged
415              
416             # forget this widget under $old_wobj
417             if ($old_wobj) {
418             _splice_out ($old_wobj->{'installed_widgets'}, $widget);
419             }
420              
421             if (! $wobj) {
422             # no wobj applies to this widget any more
423             delete $widget->{__PACKAGE__.'.installed'};
424             delete $widget->{__PACKAGE__.'.realize_ids'};
425              
426             my ($hack_win, $hack_cursor) = $widget->Gtk2_Ex_WidgetCursor_hack_restore;
427             $hack_win ||= 0; # avoid undef
428             foreach my $win ($widget->Gtk2_Ex_WidgetCursor_windows) {
429             $win || next;
430             my $cursor = ($win == $hack_win
431             ? Gtk2::Gdk::Cursor->new_for_display ($widget->get_display,
432             $hack_cursor)
433             : undef);
434             ### set_cursor back to: "$win", $cursor && $cursor->type
435             $win->set_cursor ($cursor);
436             }
437             return;
438             }
439              
440             # install wobj on this widget
441              
442             # remember this widget under wobj
443             # this is when unrealized too, so remember to cleanup realize handler
444             { my $aref = $wobj->{'installed_widgets'};
445             push @$aref, $widget;
446             Scalar::Util::weaken ($aref->[-1]);
447             ### gives installed_widgets: join(' ',@$aref)
448             }
449              
450             # note this wobj under the widget
451             $widget->{__PACKAGE__.'.installed'} = $wobj;
452             Scalar::Util::weaken ($widget->{__PACKAGE__.'.installed'});
453              
454             my @windows = $widget->Gtk2_Ex_WidgetCursor_windows;
455             if (! defined $windows[0]) {
456             ### not realized, defer setting
457             $widget->{__PACKAGE__.'.realize_ids'} ||= do {
458             require Glib::Ex::SignalIds;
459             Glib::Ex::SignalIds->new
460             ($widget,
461             $widget->signal_connect (realize => \&_do_widget_realize))
462             };
463             return;
464             }
465              
466             # and finally actually set the cursor
467             my $cursor = _resolve_cursor ($wobj, $widget);
468             foreach my $win (@windows) {
469             $win or next;
470             ### set_cursor: "$win", $cursor && $cursor->type
471             $win->set_cursor ($cursor);
472             }
473             }
474              
475             # 'realize' signal handler on a WidgetCursor affected widget
476             sub _do_widget_realize {
477             my ($widget) = @_;
478             ### _do_widget_realize(): "$widget"
479             delete $widget->{__PACKAGE__.'.realize_ids'};
480             _update_widget ($widget);
481             }
482              
483              
484             # Return true if $wobj is applicable to $widget, either because $widget is
485             # in its widgets list or is a child of one of them for "include_children".
486             # Note $w->is_ancestor($w) is false, ie. it doesn't include itself.
487             #
488             sub _wobj_applies_to_widget {
489             my ($wobj, $widget) = @_;
490             return List::Util::first
491             { defined $_ # possible weakening during destroy
492             && ($_ == $widget
493             || ($wobj->{'include_children'} && $widget->is_ancestor($_))) }
494             @{$wobj->{'widgets'}};
495             }
496              
497             # get or set "cursor"
498             sub cursor {
499             my ($self, $newval) = @_;
500             if (@_ < 2) { return $self->{'cursor'}; } # get
501              
502             # set
503             if (_cursor_equal ($self->{'cursor'}, $newval)) { return; }
504             $self->{'cursor'} = $newval;
505              
506             if ($self->{'active'}) {
507             foreach my $widget (@{$self->{'installed_widgets'}}) {
508             foreach my $win ($widget->Gtk2_Ex_WidgetCursor_windows) {
509             $win || next; # only if realized
510             $win->set_cursor (_resolve_cursor ($self, $widget));
511             }
512             }
513             }
514              
515             $self->notify('cursor');
516             $self->notify('cursor-name');
517             $self->notify('cursor-object');
518             }
519              
520             # return true if two cursor settings $x and $y are the same
521             sub _cursor_equal {
522             my ($x, $y) = @_;
523             return ((! defined $x && ! defined $y) # undef == undef
524             || (ref $x && ref $y && $x == $y) # objects identical address
525             || (defined $x && defined $y && $x eq $y)); # strings by value
526             }
527              
528             # get widgets in wobj
529             sub widgets {
530             my ($self) = @_;
531             return grep {defined} @{$self->{'widgets'}};
532             }
533              
534             sub add_widgets {
535             my ($self, @widgets) = @_;
536             my $aref = $self->{'widgets'};
537              
538             # only those not already in our list
539             @widgets = grep { my $widget = @_;
540             ! List::Util::first {defined $_ && $_==$widget}
541             @$aref } @widgets;
542             if (! @widgets) { return; }
543              
544             foreach my $widget (@widgets) {
545             push @$aref, $widget;
546             Scalar::Util::weaken ($aref->[-1]);
547             }
548              
549             if ($self->{'include_children'}) {
550             # for include_children must have a deep look down through the new
551             # widgets, let the full code of _wobj_activated() do that (though it's a
552             # little wasteful to look again at the previously covered widgets)
553             _wobj_activated ($self);
554              
555             } else {
556             # for ordinary only the newly added widgets might change
557             foreach my $widget (@widgets) {
558             _update_widget ($widget);
559             }
560             }
561             $self->notify('widget');
562             $self->notify('widgets');
563             }
564              
565             # return an actual Gtk2::Gdk::Cursor from what may be only a string setting
566             # in $wobj->{'cursor'}
567             sub _resolve_cursor {
568             my ($wobj, $widget) = @_;
569             my $cursor = $wobj->{'cursor'};
570              
571             if (! defined $cursor || ref $cursor) {
572             # undef or cursor object
573             return $cursor;
574              
575             } elsif ($cursor eq 'invisible') {
576             # call through $wobj in case of subclassing
577             return $wobj->invisible_cursor ($widget);
578              
579             } else {
580             # string cursor name -- only ever call to resolve here when widget is
581             # realized, so get_display() isn't undef
582             if ($widget->can('get_display')) {
583             # gtk 2.2 up
584             my $display = $widget->get_display;
585             return Gtk2::Gdk::Cursor->new_for_display ($display, $cursor);
586             } else {
587             # gtk 2.0.x
588             return Gtk2::Gdk::Cursor->new ($cursor);
589             }
590             }
591             }
592              
593             # Return $widget and all its contained children, grandchildren, etc.
594             # Iterative avoids deep recursion warning for the unlikely case of nesting
595             # beyond 100 deep.
596             #
597             sub _container_recursively {
598             my @pending = @_;
599             my @ret;
600             while (@pending) {
601             my $widget = pop @pending;
602             push @ret, $widget;
603             if (my $func = $widget->can('get_children')) {
604             push @pending, $widget->$func;
605             }
606             }
607             return @ret;
608             }
609              
610             #------------------------------------------------------------------------------
611             # operative windows hacks
612             #
613             # $widget->Gtk2_Ex_WidgetCursor_windows() returns a list of windows in
614             # $widget to act on, with hacks to pickup multiple windows on core classes.
615             #
616             # $widget->Gtk2_Ex_WidgetCursor_hack_restore() returns ($win, $cursor).
617             # $cursor is a string cursor name to put back on $win when there's no more
618             # WidgetCursor objects. Or the return is an empty list or $win undef when
619             # nothing to hack (in which case all windows go back to "undef" cursor).
620             #
621              
622             # default to operate on $widget->window alone
623             *Gtk2::Widget::Gtk2_Ex_WidgetCursor_windows = \&Gtk2::Widget::window;
624             sub Gtk2::Widget::Gtk2_Ex_WidgetCursor_hack_restore { return (); }
625              
626             # GtkEventBox under a GtkComboBox popup window has a 'top-left-arrow'. It
627             # gets overridden by a special case in the recursive updates above, and
628             # hack_restore() here puts it back.
629             #
630             sub Gtk2::EventBox::Gtk2_Ex_WidgetCursor_hack_restore {
631             my ($widget) = @_;
632             return _widget_is_combo_eventbox($widget)
633             && ($widget->window, 'top-left-arrow');
634             }
635              
636             # GtkTextView operate on 'text' subwindow to override its insertion point
637             # cursor there, plus the main 'widget' window to cover the entire widget
638             # extent. The 'text' subwindow insertion point is supposed to be on when
639             # the widget is sensitive, so hack_restore() that.
640             #
641             sub Gtk2::TextView::Gtk2_Ex_WidgetCursor_windows {
642             my ($widget) = @_;
643             return ($widget->get_window ('widget'),
644             $widget->get_window ('text'));
645             }
646             sub Gtk2::TextView::Gtk2_Ex_WidgetCursor_hack_restore {
647             my ($widget) = @_;
648             return $widget->sensitive && ($widget->get_window('text'), 'xterm');
649             }
650              
651             # GtkEntry's extra subwindow is included here. And when sensitive it should
652             # be put back to an insertion point. For a bit of safety use list context
653             # etc to allow for no subwindows, since it's undocumented.
654             #
655             # In Gtk 2.14 the SpinButton sub-class has the arrow panel as a subwindow
656             # too (instead of an overlay in Gtk 2.12 and earlier). So look for the
657             # smaller height one among multiple subwindows.
658             #
659             sub Gtk2::Entry::Gtk2_Ex_WidgetCursor_windows {
660             my ($widget) = @_;
661             my $win = $widget->window || return; # if unrealized
662             return ($win, $win->get_children);
663             }
664             sub Gtk2::Entry::Gtk2_Ex_WidgetCursor_hack_restore {
665             my ($widget) = @_;
666             $widget->sensitive or return;
667             my $win = $widget->window || return; # if unrealized
668             my @children = $win->get_children;
669             # by increasing height
670             @children = sort {($a->get_size)[1] <=> ($b->get_size)[1]} @children;
671             return ($children[0], 'xterm');
672             }
673             # GtkSpinButton's extra "panel" overlay window either as a "sibling" (which
674             # also finds the main window) for Gtk 2.12 or in the get_children() for Gtk
675             # 2.13; plus the GtkEntry subwindow as per GtkEntry above. hack_restore()
676             # inherited from GtkEntry above.
677             #
678             sub Gtk2::SpinButton::Gtk2_Ex_WidgetCursor_windows {
679             my ($widget) = @_;
680             my $win = $widget->window || return; # if unrealized
681             return (_widget_sibling_windows ($widget),
682             $win->get_children);
683             }
684              
685             # GtkButton secret input-only "event_window" overlay found as a "sibling".
686             #
687             sub Gtk2::Button::Gtk2_Ex_WidgetCursor_windows {
688             my ($widget) = @_;
689             return _widget_sibling_windows ($widget);
690             }
691              
692             # _widget_sibling_windows() returns a list of the "sibling" windows of
693             # $widget. This means all the windows which are under $widget's parent and
694             # have their events directed to $widget. If $widget is a windowed widget
695             # then this will include its main $widget->window (or should do).
696             #
697             # The search works by seeing where a dummy expose event is directed by
698             # gtk_get_event_widget(). It'd also be possible to inspect
699             # gdk_window_get_user_data(), but Gtk2-Perl only returns an "unsigned" for
700             # that so it'd need some nasty digging for the widget address.
701             #
702             # In the past the code here cached the result against the widget (what was
703             # then just GtkButton's "event_window" sibling), with weakening of course so
704             # unrealize would destroy the windows as normal. But don't bother with that
705             # now, on the basis that cursor changes hopefully aren't so frequent as to
706             # need too much trouble, and that it's less prone to mistakes if not cached
707             # :-).
708             #
709             sub _widget_sibling_windows {
710             my ($widget) = @_;
711             my $parent_win = ($widget->flags & 'no-window'
712             ? $widget->window
713             : $widget->get_parent_window)
714             || return; # if unrealized
715              
716             my $event = Gtk2::Gdk::Event->new ('expose');
717             return grep { $event->window ($_);
718             ($widget == (Gtk2->get_event_widget($event) || 0))
719             } $parent_win->get_children;
720             }
721              
722             # Return true if $widget is the Gtk2::EventBox child of a Gtk2::Combo popup
723             # window (it's a child of the popup window, not of the Combo itself).
724             #
725             sub _widget_is_combo_eventbox {
726             my ($widget) = @_;
727             my $parent;
728             return ($widget->isa('Gtk2::EventBox')
729             && ($parent = $widget->get_parent) # might not have a parent
730             && $parent->get_name eq 'gtk-combo-popup-window');
731             }
732              
733              
734             #------------------------------------------------------------------------------
735              
736             # Could think about documenting this idle level to the world, maybe like the
737             # following, but would it be any use?
738             #
739             # =item C<$Gtk2::Ex::WidgetCursor::busy_idle_priority>
740             #
741             # The priority level of the (C<< Glib::Idle->add >>) handler installed by
742             # C. This is C by default, which is
743             # designed to stay busy through Gtk resizing and redrawing at around
744             # C, but end the busy before ordinary "default idle"
745             # tasks.
746             #
747             # You can change this depending what things you set running at what idle
748             # levels and where you consider the application no longer busy for user
749             # purposes. But note changing this variable only affects future C
750             # calls, not any currently active one.
751             #
752             use constant BUSY_IDLE_PRIORITY => Glib::G_PRIORITY_DEFAULT_IDLE - 10;
753              
754             my $busy_wc;
755             my $busy_id;
756             my $realize_id;
757              
758             sub busy {
759             my ($class) = @_;
760             my @widgets = Gtk2::Window->list_toplevels;
761             ### busy on toplevels: join(' ',@widgets)
762              
763             if ($busy_wc) {
764             $busy_wc->add_widgets (@widgets);
765             } else {
766             ### new busy with class: $class
767             $busy_wc = $class->new (widgets => \@widgets,
768             cursor => 'watch',
769             include_children => 1,
770             priority => 1000,
771             active => 1);
772             }
773             _flush_mapped_widgets (@widgets);
774              
775             # This is a hack to persuade Gtk2-Perl 1.160 and 1.181 to finish loading
776             # Gtk2::Widget. Without this if no Gtk2::Widget has ever been created the
777             # signal_add_emission_hook() fails. 1.160 needs the combination of isa()
778             # and find_property(). 1.181 is ok with find_property() alone. Either
779             # way these can be removed when ready to depend on 1.200 and up.
780             Gtk2::Widget->isa ('Gtk2::Widget');
781             Gtk2::Widget->find_property ('name');
782            
783             $realize_id ||= Gtk2::Widget->signal_add_emission_hook
784             (realize => \&_do_busy_realize_emission);
785              
786             $busy_id ||= Glib::Idle->add
787             (\&_busy_idle_handler, undef, BUSY_IDLE_PRIORITY);
788             }
789              
790             # While busy notice extra toplevels which have been realized.
791             # The cursor setting is applied at the realize so it's there ready for when
792             # the map is done.
793             sub _do_busy_realize_emission {
794             my ($invocation_hint, $param_list) = @_;
795             my ($widget) = @$param_list;
796             ### WidgetCursor _do_busy_realize_emission(): "$widget"
797             if ($widget->isa ('Gtk2::Window')) {
798             $busy_wc->add_widgets (Gtk2::Window->list_toplevels);
799             ### _do_busy_realize_emission() flush
800             $widget->get_display->flush;
801             }
802             return 1; # stay connected
803             }
804              
805             # Call unbusy() through $busy_wc to allow for possible subclassing.
806             # Using unbusy does a flush, which is often unnecessary but will ensure that
807             # if there's lower priority idles still to run then our cursors go out
808             # before the time they take.
809             #
810             sub _busy_idle_handler {
811             ### _busy_idle_handler finished
812             $busy_id = undef;
813             if ($busy_wc) { $busy_wc->unbusy; }
814             return 0; # Glib::SOURCE_REMOVE, one run only
815             }
816              
817             sub unbusy {
818             # my ($class_or_self) = @_;
819             ### WidgetCursor unbusy()
820              
821             # Some freaky stuff can happen during perl "global destruction" with
822             # classes being destroyed and disconecting emission hooks on their own,
823             # provoking warnings from code like the following that does a cleanup
824             # itself. Fairly confident that doesn't apply to Gtk2::Widget because
825             # that class probably, hopefully, maybe, never gets destroyed, or at least
826             # not until well after any Perl code might get a chance to call unbusy().
827             #
828             if ($realize_id) {
829             Gtk2::Widget->signal_remove_emission_hook (realize => $realize_id);
830             undef $realize_id;
831             }
832              
833             if ($busy_id) {
834             Glib::Source->remove ($busy_id);
835             $busy_id = undef;
836             }
837             if ($busy_wc) {
838             my @widgets = $busy_wc->widgets;
839             $busy_wc = undef;
840             # flush to show new cursors immediately, per busy() below
841             _flush_mapped_widgets (@widgets);
842             }
843             }
844              
845             # flush the Gtk2::Gdk::Display's of all the given widgets, if they're mapped
846             # (with the idea being if they're unmapped then there's nothing to see so no
847             # need to flush)
848             #
849             sub _flush_mapped_widgets {
850             my @widget_list = @_;
851             my %done;
852             ### _flush_mapped_widgets
853             foreach my $widget (@widget_list) {
854             if ($widget->mapped) {
855             my $display = $widget->get_display;
856             $done{Scalar::Util::refaddr($display)} ||= do {
857             ### flush display: "$display"
858             $display->flush;
859             1
860             };
861             }
862             }
863             }
864              
865              
866             #------------------------------------------------------------------------------
867              
868             # list_values() creates a slew of hash records, so don't want to do that on
869             # every invisible_cursor() call. Doing it once at BEGIN time also allows
870             # the result to be inlined and the unused code discarded.
871             #
872             use constant _HAVE_BLANK_CURSOR
873             => (!! List::Util::first
874             {$_->{'nick'} eq 'blank-cursor'}
875             Glib::Type->list_values('Gtk2::Gdk::CursorType'));
876             ### _HAVE_BLANK_CURSOR: _HAVE_BLANK_CURSOR()
877              
878             sub invisible_cursor {
879             my ($class, $target) = @_;
880             my $display;
881              
882             if (! defined $target) {
883             $display = Gtk2::Gdk::Display->get_default
884             || croak 'invisible_cursor(): no default display';
885              
886             } elsif ($target->isa('Gtk2::Gdk::Display')) {
887             $display = $target;
888              
889             } else {
890             $display = $target->get_display
891             || croak "invisible_cursor(): get_display undef on $target";
892             }
893              
894             if (_HAVE_BLANK_CURSOR) {
895             # gdk_cursor_new_for_display() returns same object each time so no need
896             # to cache, though being a Glib::Boxed it's a new perl object every time
897             return Gtk2::Gdk::Cursor->new_for_display ($display,'blank-cursor');
898             } else {
899             return ($display->{__PACKAGE__.'.invisible_cursor'}
900             ||= do {
901             ### invisible_cursor() new for: "$display"
902             my $window = $display->get_default_screen->get_root_window;
903             my $mask = Gtk2::Gdk::Bitmap->create_from_data ($window,"\0",1,1);
904             my $color = Gtk2::Gdk::Color->new (0,0,0);
905             Gtk2::Gdk::Cursor->new_from_pixmap ($mask,$mask,$color,$color,0,0);
906             });
907             }
908             }
909              
910              
911             #------------------------------------------------------------------------------
912             # generic helpers
913              
914             sub _splice_out {
915             my ($aref, $target) = @_;
916             for (my $i = 0; $i < @$aref; $i++) {
917             if (! defined $aref->[$i] || $aref->[$i] == $target) {
918             splice @$aref, $i,1;
919             }
920             }
921             }
922              
923             #------------------------------------------------------------------------------
924             1;
925             __END__