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__ |