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