line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Copyright 2007, 2008, 2009, 2010, 2011 Kevin Ryde |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# This file is part of Gtk2-Ex-Dragger. |
4
|
|
|
|
|
|
|
# |
5
|
|
|
|
|
|
|
# Gtk2-Ex-Dragger is free software; you can redistribute it and/or modify |
6
|
|
|
|
|
|
|
# it under the terms of the GNU General Public License as published by the |
7
|
|
|
|
|
|
|
# Free Software Foundation; either version 3, or (at your option) any later |
8
|
|
|
|
|
|
|
# version. |
9
|
|
|
|
|
|
|
# |
10
|
|
|
|
|
|
|
# Gtk2-Ex-Dragger is distributed in the hope that it will be useful, but |
11
|
|
|
|
|
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
12
|
|
|
|
|
|
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
13
|
|
|
|
|
|
|
# for more details. |
14
|
|
|
|
|
|
|
# |
15
|
|
|
|
|
|
|
# You should have received a copy of the GNU General Public License along |
16
|
|
|
|
|
|
|
# with Gtk2-Ex-Dragger. If not, see . |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
package Gtk2::Ex::Dragger; |
19
|
2
|
|
|
2
|
|
2582
|
use 5.008; |
|
2
|
|
|
|
|
7
|
|
|
2
|
|
|
|
|
87
|
|
20
|
2
|
|
|
2
|
|
11
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
72
|
|
21
|
2
|
|
|
2
|
|
59
|
use warnings; |
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
73
|
|
22
|
2
|
|
|
2
|
|
18
|
use Carp; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
451
|
|
23
|
2
|
|
|
2
|
|
2560
|
use POSIX (); |
|
2
|
|
|
|
|
17839
|
|
|
2
|
|
|
|
|
73
|
|
24
|
2
|
|
|
2
|
|
2842
|
use Glib 1.220; # 1.220 for Glib::SOURCE_REMOVE |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
use Gtk2 1.220; # 1.220 for Gtk2::EVENT_PROPAGATE |
26
|
|
|
|
|
|
|
use List::Util qw(min max); |
27
|
|
|
|
|
|
|
use Scalar::Util; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use Glib::Ex::SignalIds; |
30
|
|
|
|
|
|
|
use Gtk2::Ex::WidgetEvents; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# uncomment this to run the ### lines |
33
|
|
|
|
|
|
|
#use Smart::Comments; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
our $VERSION = 10; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
use constant DELAY_MILLISECONDS => 250; |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
BEGIN { |
40
|
|
|
|
|
|
|
Glib::Type->register_enum ('Gtk2::Ex::Dragger::UpdatePolicy', |
41
|
|
|
|
|
|
|
'default', |
42
|
|
|
|
|
|
|
'continuous', |
43
|
|
|
|
|
|
|
'delayed', |
44
|
|
|
|
|
|
|
'discontinuous', |
45
|
|
|
|
|
|
|
); |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
use Glib::Object::Subclass |
49
|
|
|
|
|
|
|
'Glib::Object', |
50
|
|
|
|
|
|
|
properties => [ Glib::ParamSpec->object |
51
|
|
|
|
|
|
|
('widget', |
52
|
|
|
|
|
|
|
'Target widget', |
53
|
|
|
|
|
|
|
'The target widget whose contents are to be moved around. (For a widget inside a Gtk2::ViewPort this property should be the ViewPort.)', |
54
|
|
|
|
|
|
|
'Gtk2::Widget', |
55
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
Glib::ParamSpec->object |
58
|
|
|
|
|
|
|
('hadjustment', |
59
|
|
|
|
|
|
|
(do { |
60
|
|
|
|
|
|
|
my $str = 'Horizontal adjustment'; |
61
|
|
|
|
|
|
|
# translation if available |
62
|
|
|
|
|
|
|
eval { require Locale::Messages; |
63
|
|
|
|
|
|
|
Locale::Messages::dgettext('gtk20-properties',$str) |
64
|
|
|
|
|
|
|
} || $str }), |
65
|
|
|
|
|
|
|
'Horizontal adjustment to change, or undef for no horizontal drag.', |
66
|
|
|
|
|
|
|
'Gtk2::Adjustment', |
67
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
Glib::ParamSpec->object |
70
|
|
|
|
|
|
|
('vadjustment', |
71
|
|
|
|
|
|
|
(do { |
72
|
|
|
|
|
|
|
my $str = 'Vertical adjustment'; |
73
|
|
|
|
|
|
|
# translation if available |
74
|
|
|
|
|
|
|
eval { require Locale::Messages; |
75
|
|
|
|
|
|
|
Locale::Messages::dgettext('gtk20-properties',$str) |
76
|
|
|
|
|
|
|
} || $str }), |
77
|
|
|
|
|
|
|
'Vertical adjustment to change, or undef for no vertical drag.', |
78
|
|
|
|
|
|
|
'Gtk2::Adjustment', |
79
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Glib::ParamSpec->boolean |
82
|
|
|
|
|
|
|
('hinverted', |
83
|
|
|
|
|
|
|
'Horizontal inverted', |
84
|
|
|
|
|
|
|
'Whether to invert horizontal movement, for hadjustment valuess increasing to the left (ie. decreasing X coordinate).', |
85
|
|
|
|
|
|
|
0, # default no |
86
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
Glib::ParamSpec->boolean |
89
|
|
|
|
|
|
|
('vinverted', |
90
|
|
|
|
|
|
|
'Vertical inverted', |
91
|
|
|
|
|
|
|
'Whether to invert vertical movement, for vadjustment values increasing up the screen (ie. decreasing Y coordinate).', |
92
|
|
|
|
|
|
|
0, # default no |
93
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
Glib::ParamSpec->boolean |
96
|
|
|
|
|
|
|
('confine', |
97
|
|
|
|
|
|
|
'Confine pointer', |
98
|
|
|
|
|
|
|
'Confine the mouse pointer to the draggable extent per upper/lower range of the adjustments.', |
99
|
|
|
|
|
|
|
0, # default no |
100
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
Glib::ParamSpec->enum |
103
|
|
|
|
|
|
|
('update-policy', |
104
|
|
|
|
|
|
|
'Update policy', |
105
|
|
|
|
|
|
|
'How often to update the hadjustment and vadjustment objects for drag movement.', |
106
|
|
|
|
|
|
|
'Gtk2::Ex::Dragger::UpdatePolicy', |
107
|
|
|
|
|
|
|
'default', |
108
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Glib::ParamSpec->scalar |
111
|
|
|
|
|
|
|
('cursor', |
112
|
|
|
|
|
|
|
'Cursor', |
113
|
|
|
|
|
|
|
'Cursor to show while dragging, as any name or object accepted by Gtk2::Ex::WidgetCursor.', |
114
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
Glib::ParamSpec->string |
117
|
|
|
|
|
|
|
('cursor-name', |
118
|
|
|
|
|
|
|
'Cursor name', |
119
|
|
|
|
|
|
|
'Cursor to show while dragging, as cursor type enum nick, or "invisible".', |
120
|
|
|
|
|
|
|
(eval {Glib->VERSION(1.240);1} |
121
|
|
|
|
|
|
|
? undef # default |
122
|
|
|
|
|
|
|
: ''), # no undef/NULL before Perl-Glib 1.240 |
123
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
Glib::ParamSpec->boxed |
126
|
|
|
|
|
|
|
('cursor-object', |
127
|
|
|
|
|
|
|
'Cursor object', |
128
|
|
|
|
|
|
|
'Cursor to show while dragging, as cursor object.', |
129
|
|
|
|
|
|
|
'Gtk2::Gdk::Cursor', |
130
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
]; |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
sub INIT_INSTANCE { |
135
|
|
|
|
|
|
|
my ($self) = @_; |
136
|
|
|
|
|
|
|
$self->{'h'} = {}; |
137
|
|
|
|
|
|
|
$self->{'v'} = {}; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub FINALIZE_INSTANCE { |
141
|
|
|
|
|
|
|
my ($self) = @_; |
142
|
|
|
|
|
|
|
$self->stop; |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub GET_PROPERTY { |
146
|
|
|
|
|
|
|
my ($self, $pspec) = @_; |
147
|
|
|
|
|
|
|
my $pname = $pspec->get_name; |
148
|
|
|
|
|
|
|
### Dragger GET_PROPERTY(): $pname |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
if ($pname eq 'cursor_name') { |
151
|
|
|
|
|
|
|
my $cursor = $self->{'cursor'}; |
152
|
|
|
|
|
|
|
if (Scalar::Util::blessed($cursor)) { |
153
|
|
|
|
|
|
|
$cursor = $cursor->type; |
154
|
|
|
|
|
|
|
# think prefer undef over cursor-is-pixmap for the get() |
155
|
|
|
|
|
|
|
if ($cursor eq 'cursor-is-pixmap') { |
156
|
|
|
|
|
|
|
undef $cursor; |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
return $cursor; |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
if ($pname eq 'cursor_object') { |
162
|
|
|
|
|
|
|
my $cursor = $self->{'cursor'}; |
163
|
|
|
|
|
|
|
return (Scalar::Util::blessed($cursor) |
164
|
|
|
|
|
|
|
&& $cursor->isa('Gtk2::Gdk::Cursor') |
165
|
|
|
|
|
|
|
&& $cursor); |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
return $self->{$pname}; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
sub SET_PROPERTY { |
172
|
|
|
|
|
|
|
my ($self, $pspec, $newval) = @_; |
173
|
|
|
|
|
|
|
### Dragger SET_PROPERTY(): $pspec->get_name |
174
|
|
|
|
|
|
|
my $pname = $pspec->get_name; |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
my $pname_is_cursor = ($pname =~ s/^(cursor).*/$1/); |
177
|
|
|
|
|
|
|
my $oldval = $self->{$pname}; |
178
|
|
|
|
|
|
|
$self->{$pname} = $newval; |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
if ($pname_is_cursor) { |
181
|
|
|
|
|
|
|
# copy boxed GdkCursor in case the caller frees it, and in particular |
182
|
|
|
|
|
|
|
# for $pname eq 'cursor_object' it might be freed immediately by the |
183
|
|
|
|
|
|
|
# GValue call-out stuff |
184
|
|
|
|
|
|
|
# if (blessed($newval) && $newval->isa('Gtk2::Gdk::Cursor')) { |
185
|
|
|
|
|
|
|
# $self->{$pname} = $newval->copy; |
186
|
|
|
|
|
|
|
# } |
187
|
|
|
|
|
|
|
_widget_cursor($self); |
188
|
|
|
|
|
|
|
$self->notify('cursor'); |
189
|
|
|
|
|
|
|
$self->notify('cursor-name'); |
190
|
|
|
|
|
|
|
$self->notify('cursor-object'); |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
} elsif ($pname eq 'widget') { |
193
|
|
|
|
|
|
|
my $widget = $newval; |
194
|
|
|
|
|
|
|
if (! $newval |
195
|
|
|
|
|
|
|
|| ($oldval && $newval != $oldval)) { |
196
|
|
|
|
|
|
|
# ENHANCE-ME: might be able to switch to an active grab on the new |
197
|
|
|
|
|
|
|
# widget and continue |
198
|
|
|
|
|
|
|
$self->stop; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
if ($widget) { |
201
|
|
|
|
|
|
|
Scalar::Util::weaken ($self->{'widget'}); |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
$self->{'wevents'} = $widget && Gtk2::Ex::WidgetEvents->new |
204
|
|
|
|
|
|
|
($widget, ['button-press-mask', |
205
|
|
|
|
|
|
|
'button-motion-mask', |
206
|
|
|
|
|
|
|
'button-release-mask']); |
207
|
|
|
|
|
|
|
_widget_signals($self); |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
} elsif ($pname =~ /([hv])(adjustment|inverted)/) { |
210
|
|
|
|
|
|
|
my $axis = $self->{$1}; |
211
|
|
|
|
|
|
|
my $field = $2; |
212
|
|
|
|
|
|
|
$axis->{$field} = $newval; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
if ($field eq 'adjustment') { |
215
|
|
|
|
|
|
|
$axis->{'last_value'} = $newval && $newval->value; |
216
|
|
|
|
|
|
|
_axis_signals($self,$axis); |
217
|
|
|
|
|
|
|
_do_adjustment_changed ($newval, \$self); |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
} elsif ($pname eq 'confine') { |
221
|
|
|
|
|
|
|
_resize_confine_win ($self); |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# create or update WidgetCursor according to $self->{'cursor'} |
226
|
|
|
|
|
|
|
# doesn't load WidgetCursor until drag active |
227
|
|
|
|
|
|
|
sub _widget_cursor { |
228
|
|
|
|
|
|
|
my ($self) = @_; |
229
|
|
|
|
|
|
|
$self->{'wcursor'} = ($self->{'active'} |
230
|
|
|
|
|
|
|
&& $self->{'cursor'} |
231
|
|
|
|
|
|
|
&& do { |
232
|
|
|
|
|
|
|
require Gtk2::Ex::WidgetCursor; |
233
|
|
|
|
|
|
|
Gtk2::Ex::WidgetCursor->new |
234
|
|
|
|
|
|
|
(widget => $self->{'widget'}, |
235
|
|
|
|
|
|
|
cursor => $self->{'cursor'}, |
236
|
|
|
|
|
|
|
active => 1); |
237
|
|
|
|
|
|
|
}); |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
# update $self->{'h'}->{'adjustment_ids'} or $self->{'v'}->... signal handlers |
241
|
|
|
|
|
|
|
# handlers only applied while 'active' |
242
|
|
|
|
|
|
|
sub _axis_signals { |
243
|
|
|
|
|
|
|
my ($self, $axis) = @_; |
244
|
|
|
|
|
|
|
my $adj; |
245
|
|
|
|
|
|
|
$axis->{'adjustment_ids'} = |
246
|
|
|
|
|
|
|
($self->{'active'} |
247
|
|
|
|
|
|
|
&& ($adj = $axis->{'adjustment'}) |
248
|
|
|
|
|
|
|
&& do { |
249
|
|
|
|
|
|
|
my $ref_weak_self = _ref_weak ($self); |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
### _axis_signals() connect |
252
|
|
|
|
|
|
|
Glib::Ex::SignalIds->new |
253
|
|
|
|
|
|
|
($adj, |
254
|
|
|
|
|
|
|
$adj->signal_connect (changed => \&_do_adjustment_changed, |
255
|
|
|
|
|
|
|
$ref_weak_self), |
256
|
|
|
|
|
|
|
$adj->signal_connect (value_changed => \&_do_adjustment_value_changed, |
257
|
|
|
|
|
|
|
$ref_weak_self)) |
258
|
|
|
|
|
|
|
}); |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# make signal handler connections on $self->{'widget'} if active |
262
|
|
|
|
|
|
|
sub _widget_signals { |
263
|
|
|
|
|
|
|
my ($self) = @_; |
264
|
|
|
|
|
|
|
my $widget; |
265
|
|
|
|
|
|
|
$self->{'widget_ids'} = |
266
|
|
|
|
|
|
|
($self->{'active'} |
267
|
|
|
|
|
|
|
&& ($widget = $self->{'widget'}) |
268
|
|
|
|
|
|
|
&& do { |
269
|
|
|
|
|
|
|
my $ref_weak_self = _ref_weak ($self); |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
### _widget_signals() connect |
272
|
|
|
|
|
|
|
Glib::Ex::SignalIds->new |
273
|
|
|
|
|
|
|
($widget, |
274
|
|
|
|
|
|
|
$widget->signal_connect (motion_notify_event => \&_do_motion_notify, |
275
|
|
|
|
|
|
|
$ref_weak_self), |
276
|
|
|
|
|
|
|
$widget->signal_connect (button_release_event => \&_do_button_release, |
277
|
|
|
|
|
|
|
$ref_weak_self), |
278
|
|
|
|
|
|
|
$widget->signal_connect (configure_event => \&_do_configure_event, |
279
|
|
|
|
|
|
|
$ref_weak_self), |
280
|
|
|
|
|
|
|
$widget->signal_connect (grab_broken_event => \&_do_grab_broken, |
281
|
|
|
|
|
|
|
$ref_weak_self)) |
282
|
|
|
|
|
|
|
}); |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
sub start { |
286
|
|
|
|
|
|
|
my ($self, $event) = @_; |
287
|
|
|
|
|
|
|
### Dragger start() |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
# maybe a second start() call could transition to a different button, but |
290
|
|
|
|
|
|
|
# for now disallow it |
291
|
|
|
|
|
|
|
if ($self->{'active'}) { |
292
|
|
|
|
|
|
|
croak __PACKAGE__.'->start(): drag already active'; |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
(Scalar::Util::blessed($event) && $event->isa('Gtk2::Gdk::Event::Button')) |
295
|
|
|
|
|
|
|
or croak __PACKAGE__.'->start(): must have button press event'; |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
my $widget = $self->{'widget'}; |
298
|
|
|
|
|
|
|
my $win = $widget->Gtk2_Ex_Dragger_window |
299
|
|
|
|
|
|
|
or croak __PACKAGE__.'->start(): widget not realized'; |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
$self->{'active'} = 1; |
302
|
|
|
|
|
|
|
_widget_cursor($self); |
303
|
|
|
|
|
|
|
_widget_signals($self); |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
foreach my $axis ($self->{'h'}, $self->{'v'}) { |
306
|
|
|
|
|
|
|
my $adj = $axis->{'adjustment'} or next; |
307
|
|
|
|
|
|
|
$axis->{'unapplied'} = 0; |
308
|
|
|
|
|
|
|
$axis->{'pending'} = 0; |
309
|
|
|
|
|
|
|
$axis->{'last_value'} = $adj->value; |
310
|
|
|
|
|
|
|
_axis_signals ($self, $axis); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
$self->{'button'} = $event->button; |
314
|
|
|
|
|
|
|
($self->{'h'}->{'last_pixel'}, $self->{'v'}->{'last_pixel'}) |
315
|
|
|
|
|
|
|
= $event->get_root_coords; |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
if ($self->{'confine'}) { |
318
|
|
|
|
|
|
|
my $confine_win = ($self->{'confine_win'} ||= Gtk2::Gdk::Window->new |
319
|
|
|
|
|
|
|
($widget->get_root_window, |
320
|
|
|
|
|
|
|
{ window_type => 'temp', |
321
|
|
|
|
|
|
|
wclass => 'GDK_INPUT_ONLY', |
322
|
|
|
|
|
|
|
override_redirect => 1 })); |
323
|
|
|
|
|
|
|
### confine_win: "$confine_win" |
324
|
|
|
|
|
|
|
_resize_confine_win ($self); |
325
|
|
|
|
|
|
|
$confine_win->show; |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
# ENHANCE-ME: $win->get_events is a server round-trip, maybe fetch only |
328
|
|
|
|
|
|
|
# the first time, or fetch once and then mask in the widget 'events' |
329
|
|
|
|
|
|
|
# property subsequently; or something cooperating with WidgetEvents ... |
330
|
|
|
|
|
|
|
# |
331
|
|
|
|
|
|
|
### widget events: $widget->get_events.'' |
332
|
|
|
|
|
|
|
### window events: $widget->window->get_events.'' |
333
|
|
|
|
|
|
|
my $event_mask |
334
|
|
|
|
|
|
|
= ($win->get_events & ['button-press-mask', |
335
|
|
|
|
|
|
|
'pointer-motion-hint-mask', |
336
|
|
|
|
|
|
|
'structure-mask', |
337
|
|
|
|
|
|
|
'property-change-mask' ]) |
338
|
|
|
|
|
|
|
+ ['button-motion-mask', 'button-release-mask']; |
339
|
|
|
|
|
|
|
### events: "$event_mask" |
340
|
|
|
|
|
|
|
### grab to : $widget->window.'' |
341
|
|
|
|
|
|
|
### cf button press on: $event->window.'' |
342
|
|
|
|
|
|
|
### cf size win : "$win" |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
my $status = Gtk2::Gdk->pointer_grab ($widget->window, |
345
|
|
|
|
|
|
|
0, # owner events |
346
|
|
|
|
|
|
|
$event_mask, |
347
|
|
|
|
|
|
|
$confine_win, |
348
|
|
|
|
|
|
|
undef, # cursor inherited |
349
|
|
|
|
|
|
|
$event->time); |
350
|
|
|
|
|
|
|
### grab: "$status time ".$event->time |
351
|
|
|
|
|
|
|
if ($status eq 'success') { |
352
|
|
|
|
|
|
|
$self->{'grabbed'} = 1; |
353
|
|
|
|
|
|
|
} else { |
354
|
|
|
|
|
|
|
carp __PACKAGE__."->start(): cannot grab: $status"; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# 'grab-broken-event' signal on the target widget |
360
|
|
|
|
|
|
|
# |
361
|
|
|
|
|
|
|
# This event is a client-side invention of gdk, we listen to it to know when |
362
|
|
|
|
|
|
|
# gdk's grab tracking says we lost the grab due to a window unmap or another |
363
|
|
|
|
|
|
|
# grab by a different part of the program. This (almost certainly) means we |
364
|
|
|
|
|
|
|
# should stop dragging. |
365
|
|
|
|
|
|
|
# |
366
|
|
|
|
|
|
|
# Gtk2::Gdk->pointer_grab() above will itself enqueue a grab broken event if |
367
|
|
|
|
|
|
|
# the $widget->window we supply there is different from the one the implicit |
368
|
|
|
|
|
|
|
# grab of the button press was in. That can happen when there's multiple |
369
|
|
|
|
|
|
|
# GdkWindows within $widget, with all of their events dispatched to $widget. |
370
|
|
|
|
|
|
|
# For example if you put a no-window child into a Gtk2::Viewport then a |
371
|
|
|
|
|
|
|
# button press on it goes to the "view_window" sub-window of the Viewport, |
372
|
|
|
|
|
|
|
# which is the large moving subwindow of $widget->window (in fact |
373
|
|
|
|
|
|
|
# sub-sub-window, since there's a "bin_window" in between too). |
374
|
|
|
|
|
|
|
# |
375
|
|
|
|
|
|
|
# The code here checks if $event->window is our pointer_grab() |
376
|
|
|
|
|
|
|
# $widget->window losing the grab. (A pointer_grab() call asking for the |
377
|
|
|
|
|
|
|
# same window as currently holding the grab doesn't produce a grab broken |
378
|
|
|
|
|
|
|
# event, so that test is safe against a button press and grab to the same |
379
|
|
|
|
|
|
|
# child window.) |
380
|
|
|
|
|
|
|
# |
381
|
|
|
|
|
|
|
# It'd also be possible to look at $event->grab_window to see who has the |
382
|
|
|
|
|
|
|
# current grab window, to see if it's our desired $widget->window. That'd |
383
|
|
|
|
|
|
|
# be a kind of more positive test, but $event->grab_window is not wrapped |
384
|
|
|
|
|
|
|
# until Gtk2-Perl 1.190. |
385
|
|
|
|
|
|
|
# |
386
|
|
|
|
|
|
|
sub _do_grab_broken { |
387
|
|
|
|
|
|
|
my ($widget, $event, $ref_weak_self) = @_; |
388
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return Gtk2::EVENT_PROPAGATE; |
389
|
|
|
|
|
|
|
### Dragger _do_grab_broken() |
390
|
|
|
|
|
|
|
### event window : $event->window.'' |
391
|
|
|
|
|
|
|
### widget window: $widget->window.'' |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
if ($self->{'grabbed'} && $event->window == $widget->window) { |
394
|
|
|
|
|
|
|
$self->{'grabbed'} = 0; |
395
|
|
|
|
|
|
|
$self->stop ($event); |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
return Gtk2::EVENT_PROPAGATE; |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
# 'button-release-event' signal on the target widget |
401
|
|
|
|
|
|
|
sub _do_button_release { |
402
|
|
|
|
|
|
|
my ($widget, $event, $ref_weak_self) = @_; |
403
|
|
|
|
|
|
|
### Dragger _do_button_release() |
404
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return Gtk2::EVENT_PROPAGATE; |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
if ($event->button == $self->{'button'}) { |
407
|
|
|
|
|
|
|
_do_motion_notify ($widget, $event, \$self); # final position |
408
|
|
|
|
|
|
|
$self->stop ($event); |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
return Gtk2::EVENT_PROPAGATE; |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
sub stop { |
414
|
|
|
|
|
|
|
my ($self, $event) = @_; |
415
|
|
|
|
|
|
|
### Dragger stop() |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
if (! delete $self->{'active'}) { return; } |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
if (delete $self->{'grabbed'}) { |
420
|
|
|
|
|
|
|
Gtk2::Gdk->pointer_ungrab (defined $event |
421
|
|
|
|
|
|
|
? $event->time : Gtk2::GDK_CURRENT_TIME); |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
if (my $confine_win = $self->{'confine_win'}) { |
424
|
|
|
|
|
|
|
$confine_win->hide; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
if (my $id = delete $self->{'idle_id'}) { |
427
|
|
|
|
|
|
|
Glib::Source->remove ($id); |
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
if (my $id = delete $self->{'timer_id'}) { |
430
|
|
|
|
|
|
|
Glib::Source->remove ($id); |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
delete $self->{'widget_ids'}; |
433
|
|
|
|
|
|
|
delete $self->{'wcursor'}; |
434
|
|
|
|
|
|
|
delete $self->{'h'}->{'adjustment_ids'}; |
435
|
|
|
|
|
|
|
delete $self->{'v'}->{'adjustment_ids'}; |
436
|
|
|
|
|
|
|
_emit_pending ($self); |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
# 'configure-event' signal on the target widget |
440
|
|
|
|
|
|
|
sub _do_configure_event { |
441
|
|
|
|
|
|
|
my ($widget, $event, $ref_weak_self) = @_; |
442
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return Gtk2::EVENT_PROPAGATE; |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
# new window size changes the scale factor and hence how many pixels to |
445
|
|
|
|
|
|
|
# the adjustable limits |
446
|
|
|
|
|
|
|
_resize_confine_win ($self); |
447
|
|
|
|
|
|
|
return Gtk2::EVENT_PROPAGATE; |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
# 'changed' signal on either hadjustment or vadjustment |
451
|
|
|
|
|
|
|
sub _do_adjustment_changed { |
452
|
|
|
|
|
|
|
my ($adj, $ref_weak_self) = @_; |
453
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return; |
454
|
|
|
|
|
|
|
### Dragger _do_adjustment_changed(): "$adj" |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
# new page size changes the scale factor and hence how many pixels to the |
457
|
|
|
|
|
|
|
# adjustable limits |
458
|
|
|
|
|
|
|
_resize_confine_win ($self); |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
# 'value-changed' signal on either hadjustment or vadjustment |
462
|
|
|
|
|
|
|
# |
463
|
|
|
|
|
|
|
# If the value we see is what we set then no action. If it's something |
464
|
|
|
|
|
|
|
# different then it's a change made by the keyboard or something else |
465
|
|
|
|
|
|
|
# external. |
466
|
|
|
|
|
|
|
# |
467
|
|
|
|
|
|
|
# We must reset any 'unapplied' amount because we can't have a non-zero |
468
|
|
|
|
|
|
|
# unapplied when the value is somewhere not at the upper or lower limits, |
469
|
|
|
|
|
|
|
# because unapplied is essentially how far beyond those limits the mouse is. |
470
|
|
|
|
|
|
|
# (The effect of leaving an 'unapplied' is for the value to jump down or up |
471
|
|
|
|
|
|
|
# unnaturally on the next drag update.) |
472
|
|
|
|
|
|
|
# |
473
|
|
|
|
|
|
|
sub _do_adjustment_value_changed { |
474
|
|
|
|
|
|
|
my ($adj, $ref_weak_self) = @_; |
475
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return; |
476
|
|
|
|
|
|
|
my $axis = ($adj == ($self->{'h'}->{'adjustment'} || 0) |
477
|
|
|
|
|
|
|
? $self->{'h'} |
478
|
|
|
|
|
|
|
: $self->{'v'}); |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
if ($adj->value == $axis->{'last_value'}) { return; } |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
### Dragger value changed externally to: $adj->value |
483
|
|
|
|
|
|
|
$axis->{'last_value'} = $axis->{'adjustment'}->value; |
484
|
|
|
|
|
|
|
$axis->{'unapplied'} = 0; |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
# new positions for the limits relative to the mouse position |
487
|
|
|
|
|
|
|
_resize_confine_win ($self); |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
sub _resize_confine_win { |
491
|
|
|
|
|
|
|
my ($self) = @_; |
492
|
|
|
|
|
|
|
my $confine_win = $self->{'confine_win'} || return; |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
my $widget = $self->{'widget'}; |
495
|
|
|
|
|
|
|
my $win = $widget->Gtk2_Ex_Dragger_window; |
496
|
|
|
|
|
|
|
my ($win_width, $win_height) = $win->get_size; |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
my $root = $widget->get_root_window; |
499
|
|
|
|
|
|
|
my ($root_width, $root_height) = $root->get_size; |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
# default full root window, no confine |
502
|
|
|
|
|
|
|
my $confine_x = 0; |
503
|
|
|
|
|
|
|
my $confine_y = 0; |
504
|
|
|
|
|
|
|
my $confine_width = $root_width; |
505
|
|
|
|
|
|
|
my $confine_height = $root_height; |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
# The x position is so a move that far to the left would hit the limit of |
508
|
|
|
|
|
|
|
# the adjustment. For normal direction a mouse move to the left increases |
509
|
|
|
|
|
|
|
# adjustment value, so look at how far "value" is from "upper - page". |
510
|
|
|
|
|
|
|
# For inverted a move to the left decreases adjustment value, so look at |
511
|
|
|
|
|
|
|
# how far "value" is from "lower". |
512
|
|
|
|
|
|
|
# |
513
|
|
|
|
|
|
|
if (my $hadj = $self->{'h'}->{'adjustment'}) { |
514
|
|
|
|
|
|
|
if (my $page_size = $hadj->page_size) { |
515
|
|
|
|
|
|
|
$confine_x = $self->{'h'}->{'last_pixel'} - |
516
|
|
|
|
|
|
|
($win_width / $page_size) |
517
|
|
|
|
|
|
|
* ($self->{'h'}->{'inverted'} |
518
|
|
|
|
|
|
|
? $hadj->value - $hadj->lower |
519
|
|
|
|
|
|
|
: $hadj->upper - $hadj->page_size - $hadj->value); |
520
|
|
|
|
|
|
|
$confine_width = $win_width |
521
|
|
|
|
|
|
|
* ($hadj->upper - $hadj->lower - $hadj->page_size) / $hadj->page_size; |
522
|
|
|
|
|
|
|
} |
523
|
|
|
|
|
|
|
} |
524
|
|
|
|
|
|
|
if (my $vadj = $self->{'v'}->{'adjustment'}) { |
525
|
|
|
|
|
|
|
if (my $page_size = $vadj->page_size) { |
526
|
|
|
|
|
|
|
$confine_y = $self->{'v'}->{'last_pixel'} - |
527
|
|
|
|
|
|
|
($win_height / $page_size) |
528
|
|
|
|
|
|
|
* ($self->{'v'}->{'inverted'} |
529
|
|
|
|
|
|
|
? $vadj->value - $vadj->lower |
530
|
|
|
|
|
|
|
: $vadj->upper - $vadj->page_size - $vadj->value); |
531
|
|
|
|
|
|
|
$confine_height = $win_height |
532
|
|
|
|
|
|
|
* ($vadj->upper - $vadj->lower - $vadj->page_size) / $vadj->page_size; |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
# round x,y down to integers, increasing width,height by what's subtracted |
537
|
|
|
|
|
|
|
{ |
538
|
|
|
|
|
|
|
my $frac; |
539
|
|
|
|
|
|
|
($confine_x, $frac) = _floor_and_frac ($confine_x); |
540
|
|
|
|
|
|
|
$confine_width += $frac; |
541
|
|
|
|
|
|
|
($confine_y, $frac) = _floor_and_frac ($confine_y); |
542
|
|
|
|
|
|
|
$confine_height += $frac; |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
# round up width,height to integers |
546
|
|
|
|
|
|
|
$confine_width = POSIX::ceil ($confine_width); |
547
|
|
|
|
|
|
|
$confine_height = POSIX::ceil ($confine_height); |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
# allow an extra pixel left,right,top and bottom just in case the rounding |
550
|
|
|
|
|
|
|
# is a bit off, or whatever |
551
|
|
|
|
|
|
|
$confine_x--; |
552
|
|
|
|
|
|
|
$confine_y--; |
553
|
|
|
|
|
|
|
$confine_width += 2; |
554
|
|
|
|
|
|
|
$confine_height += 2; |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
# Bring any negative top-left X,Y into range of the screen. This is in |
557
|
|
|
|
|
|
|
# case X,Y are big negatives that overflow the signed 16-bit value in the |
558
|
|
|
|
|
|
|
# X protocol. |
559
|
|
|
|
|
|
|
if ($confine_x < 0) { |
560
|
|
|
|
|
|
|
$confine_width += $confine_x; # reduce width accordingly |
561
|
|
|
|
|
|
|
$confine_x = 0; |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
if ($confine_y < 0) { |
564
|
|
|
|
|
|
|
$confine_height += $confine_y; # reduce height accordingly |
565
|
|
|
|
|
|
|
$confine_y = 0; |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
# If the X,Y position is off the right or bottom of the screen then go to |
569
|
|
|
|
|
|
|
# a single pixel at that right or bottom. Suspect this shouldn't occur, |
570
|
|
|
|
|
|
|
# because the confine window will contain the current mouse position, and |
571
|
|
|
|
|
|
|
# that's certainly somewhere on-screen. |
572
|
|
|
|
|
|
|
# |
573
|
|
|
|
|
|
|
if ($confine_x >= $root_width) { |
574
|
|
|
|
|
|
|
$confine_x = $root_width - 1; |
575
|
|
|
|
|
|
|
$confine_width = 1; |
576
|
|
|
|
|
|
|
} |
577
|
|
|
|
|
|
|
if ($confine_y >= $root_height) { |
578
|
|
|
|
|
|
|
$confine_y = $root_height - 1; |
579
|
|
|
|
|
|
|
$confine_height = 1; |
580
|
|
|
|
|
|
|
} |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
# Chop off any width/height exceeding the screen. This is in case |
583
|
|
|
|
|
|
|
# width,height are big values which overflow the 16-bit integers in the X |
584
|
|
|
|
|
|
|
# protocol. |
585
|
|
|
|
|
|
|
$confine_width = min ($confine_width, $root_width - $confine_x); |
586
|
|
|
|
|
|
|
$confine_height = min ($confine_height, $root_height - $confine_y); |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
### confine to: "$confine_x,$confine_y ${confine_width}x${confine_height}" |
589
|
|
|
|
|
|
|
$confine_win->move_resize ($confine_x, $confine_y, |
590
|
|
|
|
|
|
|
$confine_width, $confine_height); |
591
|
|
|
|
|
|
|
} |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
# 'motion-notify-event' on widget, and also called for button release. |
594
|
|
|
|
|
|
|
# |
595
|
|
|
|
|
|
|
# The basic operation is simply to look at how many pixels the new x,y in |
596
|
|
|
|
|
|
|
# $event has moved from our last_x,last_y and apply those amounts to the |
597
|
|
|
|
|
|
|
# "value" in the adjustments. But with attention to the following: |
598
|
|
|
|
|
|
|
# |
599
|
|
|
|
|
|
|
# * Scale factor $value_per_pixel converts between a window worth of pixels |
600
|
|
|
|
|
|
|
# equivalent to a page size amount in the adjust. |
601
|
|
|
|
|
|
|
# |
602
|
|
|
|
|
|
|
# * last_x and last_y are kept in root window coordinates. This makes no |
603
|
|
|
|
|
|
|
# difference to each "delta" calculated, but means we're safe against any |
604
|
|
|
|
|
|
|
# changes to the widget window position; and also makes the confine_win |
605
|
|
|
|
|
|
|
# calculation a little easier. |
606
|
|
|
|
|
|
|
# |
607
|
|
|
|
|
|
|
# * The hinverted/vinverted settings are tricky to get the right way around. |
608
|
|
|
|
|
|
|
# In normal state a move to the right reduces the value, and when inverted |
609
|
|
|
|
|
|
|
# it's the other way around. |
610
|
|
|
|
|
|
|
# |
611
|
|
|
|
|
|
|
# * An "unapplied" amount of value is maintained horizontally and vertically |
612
|
|
|
|
|
|
|
# if the prospective value would be outside the adjustment upper/lower |
613
|
|
|
|
|
|
|
# bounds. It gets added back each time, with the effect of keeping the |
614
|
|
|
|
|
|
|
# same widget contents position under the mouse if you go beyond the limit |
615
|
|
|
|
|
|
|
# and then come back. |
616
|
|
|
|
|
|
|
# |
617
|
|
|
|
|
|
|
sub _do_motion_notify { |
618
|
|
|
|
|
|
|
my ($widget, $event, $ref_weak_self) = @_; |
619
|
|
|
|
|
|
|
#### Dragger _do_motion_notify() |
620
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return Gtk2::EVENT_PROPAGATE; |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
# Believe no need for Gtk 2.12 $event->request_motions here since our |
623
|
|
|
|
|
|
|
# device is only ever the mouse, so $win->get_pointer is enough. Besides, |
624
|
|
|
|
|
|
|
# request_motions() looks pretty slack -- surely if you're going to do a |
625
|
|
|
|
|
|
|
# server round trip (as $disp->get_pointer or $device->get_state) then you |
626
|
|
|
|
|
|
|
# should use the position obtained, not throw it away. |
627
|
|
|
|
|
|
|
# |
628
|
|
|
|
|
|
|
# test can('is_hint') to allow for final $event a Gtk2::Gdk::Event::Button |
629
|
|
|
|
|
|
|
# release; such an event doesn't have an is_hint field. |
630
|
|
|
|
|
|
|
# |
631
|
|
|
|
|
|
|
my ($x, $y); |
632
|
|
|
|
|
|
|
if ($event->can('is_hint') && $event->is_hint) { |
633
|
|
|
|
|
|
|
(undef, $x, $y) = $widget->get_root_window->get_pointer; |
634
|
|
|
|
|
|
|
} else { |
635
|
|
|
|
|
|
|
($x, $y) = $event->get_root_coords; |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
my $win = $widget->Gtk2_Ex_Dragger_window; |
639
|
|
|
|
|
|
|
my ($win_width, $win_height) = $win->get_size; |
640
|
|
|
|
|
|
|
_set_value ($self, $self->{'h'}, $win_width, $x); |
641
|
|
|
|
|
|
|
_set_value ($self, $self->{'v'}, $win_height, $y); |
642
|
|
|
|
|
|
|
return Gtk2::EVENT_PROPAGATE; |
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
sub _set_value { |
646
|
|
|
|
|
|
|
my ($self, $axis, $win_size, $pixel) = @_; |
647
|
|
|
|
|
|
|
##### Dragger _set_value(): "pixel $pixel", $axis |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
my $adj = $axis->{'adjustment'} || return; |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
my $delta_pixel = $pixel - $axis->{'last_pixel'}; |
652
|
|
|
|
|
|
|
if ($delta_pixel == 0) { return; } |
653
|
|
|
|
|
|
|
$axis->{'last_pixel'} = $pixel; |
654
|
|
|
|
|
|
|
if ($axis->{'inverted'}) { $delta_pixel = - $delta_pixel; } |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
my $page_size = $adj->page_size; |
657
|
|
|
|
|
|
|
my $value_per_pixel = $page_size / $win_size; |
658
|
|
|
|
|
|
|
my $old_value = $adj->value; |
659
|
|
|
|
|
|
|
my $new_value |
660
|
|
|
|
|
|
|
= $old_value + $axis->{'unapplied'} - $delta_pixel * $value_per_pixel; |
661
|
|
|
|
|
|
|
my $unapplied = 0; |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
my $lower = $adj->lower; |
664
|
|
|
|
|
|
|
if ($new_value < $lower) { |
665
|
|
|
|
|
|
|
$unapplied = $new_value - $lower; # negative |
666
|
|
|
|
|
|
|
$new_value = $lower; |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
my $upper = $adj->upper - $page_size; |
669
|
|
|
|
|
|
|
if ($new_value > $upper) { |
670
|
|
|
|
|
|
|
$unapplied = $new_value - $upper; # positive |
671
|
|
|
|
|
|
|
$new_value = $upper; |
672
|
|
|
|
|
|
|
} |
673
|
|
|
|
|
|
|
$axis->{'unapplied'} = $unapplied; |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
### set value: $new_value |
676
|
|
|
|
|
|
|
$adj->value ($new_value); |
677
|
|
|
|
|
|
|
$new_value = $axis->{'last_value'} = $adj->value; # refetch in case rounding |
678
|
|
|
|
|
|
|
### rounded from NV: $new_value |
679
|
|
|
|
|
|
|
if ($old_value == $new_value) { |
680
|
|
|
|
|
|
|
### unchanged, no signals |
681
|
|
|
|
|
|
|
return; |
682
|
|
|
|
|
|
|
} |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
$adj->notify ('value'); |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
my $update_policy = $self->{'update_policy'} || 'default'; |
687
|
|
|
|
|
|
|
if ($update_policy eq 'continuous') { |
688
|
|
|
|
|
|
|
# emit on every set |
689
|
|
|
|
|
|
|
$adj->value_changed; |
690
|
|
|
|
|
|
|
return; |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
$axis->{'pending'} = 1; |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
if ($update_policy eq 'discontinuous') { |
696
|
|
|
|
|
|
|
# don't emit at all until stop |
697
|
|
|
|
|
|
|
return; |
698
|
|
|
|
|
|
|
} |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
if ($update_policy eq 'delayed') { |
701
|
|
|
|
|
|
|
$self->{'timer_id'} ||= Glib::Timeout->add |
702
|
|
|
|
|
|
|
(DELAY_MILLISECONDS, \&_do_timer_delayed, _ref_weak ($self)); |
703
|
|
|
|
|
|
|
return; |
704
|
|
|
|
|
|
|
} |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
# default policy |
707
|
|
|
|
|
|
|
require Gtk2::Ex::SyncCall; |
708
|
|
|
|
|
|
|
if (! $self->{'sync_obj'} && ! $self->{'timer_id'}) { |
709
|
|
|
|
|
|
|
#### Dragger SyncCall send |
710
|
|
|
|
|
|
|
my $ref_weak_self = _ref_weak ($self); |
711
|
|
|
|
|
|
|
$self->{'sync_obj'} = Gtk2::Ex::SyncCall->sync |
712
|
|
|
|
|
|
|
($self->{'widget'}, \&_do_sync, $ref_weak_self); |
713
|
|
|
|
|
|
|
$self->{'timer_id'} = Glib::Timeout->add |
714
|
|
|
|
|
|
|
(DELAY_MILLISECONDS, \&_do_timer_sync, $ref_weak_self); |
715
|
|
|
|
|
|
|
} |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
# timer expiry for 'delayed' policy |
719
|
|
|
|
|
|
|
# emit 'value-changed' when the timer expires |
720
|
|
|
|
|
|
|
# |
721
|
|
|
|
|
|
|
sub _do_timer_delayed { |
722
|
|
|
|
|
|
|
my ($ref_weak_self) = @_; |
723
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return Glib::SOURCE_REMOVE; |
724
|
|
|
|
|
|
|
#### Dragger _do_timer_delayed() |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
$self->{'timer_id'} = 0; |
727
|
|
|
|
|
|
|
_emit_pending ($self); |
728
|
|
|
|
|
|
|
return Glib::SOURCE_REMOVE; |
729
|
|
|
|
|
|
|
} |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
# sync response for 'default' policy |
732
|
|
|
|
|
|
|
# |
733
|
|
|
|
|
|
|
# At this point we wait for the timer or for idle, whichever comes first. |
734
|
|
|
|
|
|
|
# It's possible the timer has already gone off (zeroing 'timer_id'), if |
735
|
|
|
|
|
|
|
# that's the case them we emit immediately; otherwise start an idle. |
736
|
|
|
|
|
|
|
# |
737
|
|
|
|
|
|
|
sub _do_sync { |
738
|
|
|
|
|
|
|
my ($ref_weak_self) = @_; |
739
|
|
|
|
|
|
|
#### Dragger _do_sync() |
740
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return; |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
$self->{'sync_obj'} = 0; |
743
|
|
|
|
|
|
|
if ($self->{'timer_id'}) { |
744
|
|
|
|
|
|
|
$self->{'idle_id'} ||= Glib::Idle->add |
745
|
|
|
|
|
|
|
(\&_do_idle, _ref_weak ($self), Gtk2::GDK_PRIORITY_REDRAW - 1); |
746
|
|
|
|
|
|
|
} else { |
747
|
|
|
|
|
|
|
_emit_pending ($self); |
748
|
|
|
|
|
|
|
} |
749
|
|
|
|
|
|
|
} |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
# timer expiry for 'default' policy |
752
|
|
|
|
|
|
|
# |
753
|
|
|
|
|
|
|
# If the sync response hasn't yet been received then we do nothing, instead |
754
|
|
|
|
|
|
|
# wait for that. If it has been received then we can emit now, and cancel |
755
|
|
|
|
|
|
|
# the idle that was running. |
756
|
|
|
|
|
|
|
# |
757
|
|
|
|
|
|
|
sub _do_timer_sync { |
758
|
|
|
|
|
|
|
my ($ref_weak_self) = @_; |
759
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return Glib::SOURCE_REMOVE; |
760
|
|
|
|
|
|
|
#### Dragger _do_timer_sync() with sync: "$self->{'sync_obj'}" |
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
if (my $id = $self->{'idle_id'}) { |
763
|
|
|
|
|
|
|
$self->{'idle_id'} = 0; |
764
|
|
|
|
|
|
|
Glib::Source->remove ($id); |
765
|
|
|
|
|
|
|
} |
766
|
|
|
|
|
|
|
$self->{'timer_id'} = 0; |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
if (! $self->{'sync_obj'}) { |
769
|
|
|
|
|
|
|
_emit_pending ($self); |
770
|
|
|
|
|
|
|
} |
771
|
|
|
|
|
|
|
return Glib::SOURCE_REMOVE; |
772
|
|
|
|
|
|
|
} |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
# idle handler for 'default' policy |
775
|
|
|
|
|
|
|
sub _do_idle { |
776
|
|
|
|
|
|
|
my ($ref_weak_self) = @_; |
777
|
|
|
|
|
|
|
#### Dragger _do_idle() after sync |
778
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return Glib::SOURCE_REMOVE; |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
if (my $id = $self->{'timer_id'}) { |
781
|
|
|
|
|
|
|
$self->{'timer_id'} = 0; |
782
|
|
|
|
|
|
|
Glib::Source->remove ($id); |
783
|
|
|
|
|
|
|
} |
784
|
|
|
|
|
|
|
$self->{'idle_id'} = 0; |
785
|
|
|
|
|
|
|
_emit_pending ($self); |
786
|
|
|
|
|
|
|
return Glib::SOURCE_REMOVE; |
787
|
|
|
|
|
|
|
} |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
sub _emit_pending { |
790
|
|
|
|
|
|
|
my ($self) = @_; |
791
|
|
|
|
|
|
|
foreach my $axis ($self->{'h'}, $self->{'v'}) { |
792
|
|
|
|
|
|
|
if ($axis->{'pending'}) { |
793
|
|
|
|
|
|
|
$axis->{'pending'} = 0; |
794
|
|
|
|
|
|
|
$axis->{'adjustment'}->value_changed; |
795
|
|
|
|
|
|
|
} |
796
|
|
|
|
|
|
|
} |
797
|
|
|
|
|
|
|
} |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
800
|
|
|
|
|
|
|
# $widget->Gtk2_Ex_Dragger_window() returns the window in $widget which the |
801
|
|
|
|
|
|
|
# dragger should operate on (the size to page conversion). |
802
|
|
|
|
|
|
|
# |
803
|
|
|
|
|
|
|
# Crib notes: |
804
|
|
|
|
|
|
|
# |
805
|
|
|
|
|
|
|
# GtkLayout, and subclasses like GnomeCanvas |
806
|
|
|
|
|
|
|
# Plain $widget->window is the visible extent, so nothing special |
807
|
|
|
|
|
|
|
# needed. The scrolls move the bin_window subwindow, but how scrolling |
808
|
|
|
|
|
|
|
# is drawn doesn't matter to us. |
809
|
|
|
|
|
|
|
# |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
sub Gtk2::Widget::Gtk2_Ex_Dragger_window { |
812
|
|
|
|
|
|
|
my ($widget) = @_; |
813
|
|
|
|
|
|
|
if (exists $widget->{'Gtk2_Ex_Dragger_window'}) { |
814
|
|
|
|
|
|
|
# user override -- but this not (yet) a documented feature as such |
815
|
|
|
|
|
|
|
return $widget->{'Gtk2_Ex_Dragger_window'}; |
816
|
|
|
|
|
|
|
} |
817
|
|
|
|
|
|
|
# default |
818
|
|
|
|
|
|
|
return $widget->window; |
819
|
|
|
|
|
|
|
} |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
# for TextView the "text" window is the visible extent |
822
|
|
|
|
|
|
|
sub Gtk2::TextView::Gtk2_Ex_Dragger_window { |
823
|
|
|
|
|
|
|
my ($textview) = @_; |
824
|
|
|
|
|
|
|
return $textview->get_window ('text'); |
825
|
|
|
|
|
|
|
} |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
# for TreeView the "bin" window is the visible extent |
828
|
|
|
|
|
|
|
*Gtk2::TreeView::Gtk2_Ex_Dragger_window |
829
|
|
|
|
|
|
|
= \&Gtk2::TreeView::get_bin_window; |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
# For Viewport there's $widget->window then within that a "view_window" |
832
|
|
|
|
|
|
|
# which is smaller by the border size. This view_window is the scrollable |
833
|
|
|
|
|
|
|
# part we're interested in, but it's not a documented feature, so this is a |
834
|
|
|
|
|
|
|
# nasty hack to pick it out. |
835
|
|
|
|
|
|
|
# |
836
|
|
|
|
|
|
|
# The $viewport->get_bin_window is a sub-window of the view_window. It |
837
|
|
|
|
|
|
|
# contains the viewport children. (In the gtk manual in gtk 2.20 but not |
838
|
|
|
|
|
|
|
# explained as such.) |
839
|
|
|
|
|
|
|
# |
840
|
|
|
|
|
|
|
sub Gtk2::Viewport::Gtk2_Ex_Dragger_window { |
841
|
|
|
|
|
|
|
my ($viewport) = @_; |
842
|
|
|
|
|
|
|
my $win; |
843
|
|
|
|
|
|
|
return (($win = $viewport->window) # if realized |
844
|
|
|
|
|
|
|
&& ($win->get_children)[0]); |
845
|
|
|
|
|
|
|
} |
846
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
849
|
|
|
|
|
|
|
# generic helpers |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
# Return two values ($floor, $frac). |
852
|
|
|
|
|
|
|
# $floor is $x rounded down to an integer towards negative infinity |
853
|
|
|
|
|
|
|
# $frac is the fractional part subtracted from $x to get to $floor, |
854
|
|
|
|
|
|
|
# so $floor+$frac == $x |
855
|
|
|
|
|
|
|
# |
856
|
|
|
|
|
|
|
sub _floor_and_frac { |
857
|
|
|
|
|
|
|
my ($x) = @_; |
858
|
|
|
|
|
|
|
my $f = POSIX::floor ($x); |
859
|
|
|
|
|
|
|
return ($f, $x - $f); |
860
|
|
|
|
|
|
|
} |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
sub _ref_weak { |
863
|
|
|
|
|
|
|
my ($self) = @_; |
864
|
|
|
|
|
|
|
Scalar::Util::weaken ($self); |
865
|
|
|
|
|
|
|
return \$self; |
866
|
|
|
|
|
|
|
} |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
1; |
872
|
|
|
|
|
|
|
__END__ |