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-TickerView. |
4
|
|
|
|
|
|
|
# |
5
|
|
|
|
|
|
|
# Gtk2-Ex-TickerView 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-TickerView 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-TickerView. If not, see . |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
package Gtk2::Ex::TickerView; |
19
|
3
|
|
|
3
|
|
3089
|
use 5.008; |
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
113
|
|
20
|
3
|
|
|
3
|
|
16
|
use strict; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
78
|
|
21
|
3
|
|
|
3
|
|
13
|
use warnings; |
|
3
|
|
|
|
|
14
|
|
|
3
|
|
|
|
|
76
|
|
22
|
3
|
|
|
3
|
|
15
|
use Carp; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
278
|
|
23
|
3
|
|
|
3
|
|
18
|
use List::Util qw(min max); |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
422
|
|
24
|
3
|
|
|
3
|
|
2534
|
use POSIX (); |
|
3
|
|
|
|
|
21885
|
|
|
3
|
|
|
|
|
78
|
|
25
|
3
|
|
|
3
|
|
239085
|
use Time::HiRes; |
|
3
|
|
|
|
|
6081
|
|
|
3
|
|
|
|
|
17
|
|
26
|
|
|
|
|
|
|
|
27
|
3
|
|
|
3
|
|
7319
|
use Glib; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# version 1.180 for Gtk2::CellLayout as an interface, also 1.180 for |
29
|
|
|
|
|
|
|
# Gtk2::Buildable overriding superclass interface |
30
|
|
|
|
|
|
|
use Gtk2 1.180; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
use Gtk2::Ex::SyncCall 12; # version 12 for gtk XID workaround |
33
|
|
|
|
|
|
|
use Gtk2::Ex::CellLayout::Base 4; # version 4 for _cellinfo_starts() |
34
|
|
|
|
|
|
|
our @ISA; |
35
|
|
|
|
|
|
|
push @ISA, 'Gtk2::Ex::CellLayout::Base'; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
our $VERSION = 15; |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# set this to 1 for some diagnostic prints, or 2 for even more prints |
40
|
|
|
|
|
|
|
use constant DEBUG => 0; |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
use constant { DEFAULT_FRAME_RATE => 4, # times per second |
43
|
|
|
|
|
|
|
DEFAULT_SPEED => 30, # pixels per second |
44
|
|
|
|
|
|
|
}; |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# not wrapped until Gtk2-Perl 1.200 |
47
|
|
|
|
|
|
|
use constant GDK_PRIORITY_REDRAW => (Glib::G_PRIORITY_HIGH_IDLE + 20); |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
use Glib::Object::Subclass |
50
|
|
|
|
|
|
|
'Gtk2::DrawingArea', |
51
|
|
|
|
|
|
|
interfaces => |
52
|
|
|
|
|
|
|
[ 'Gtk2::CellLayout', |
53
|
|
|
|
|
|
|
# Gtk2::Buildable new in Gtk 2.12, omit if not available |
54
|
|
|
|
|
|
|
Gtk2::Widget->isa('Gtk2::Buildable') ? ('Gtk2::Buildable') : () |
55
|
|
|
|
|
|
|
], |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
signals => { expose_event => \&_do_expose_event, |
58
|
|
|
|
|
|
|
size_request => \&_do_size_request, |
59
|
|
|
|
|
|
|
size_allocate => \&_do_size_allocate, |
60
|
|
|
|
|
|
|
button_press_event => \&_do_button_press_event, |
61
|
|
|
|
|
|
|
motion_notify_event => \&_do_motion_notify_event, |
62
|
|
|
|
|
|
|
button_release_event => \&_do_button_release_event, |
63
|
|
|
|
|
|
|
scroll_event => \&_do_scroll_event, |
64
|
|
|
|
|
|
|
visibility_notify_event => \&_do_visibility_notify_event, |
65
|
|
|
|
|
|
|
direction_changed => \&_do_direction_changed, |
66
|
|
|
|
|
|
|
map => \&_do_map_or_unmap, |
67
|
|
|
|
|
|
|
unmap => \&_do_map_or_unmap, |
68
|
|
|
|
|
|
|
unrealize => \&_do_unrealize, |
69
|
|
|
|
|
|
|
notify => \&_do_notify, |
70
|
|
|
|
|
|
|
state_changed => \&_do_state_or_style_changed, |
71
|
|
|
|
|
|
|
style_set => \&_do_state_or_style_changed, |
72
|
|
|
|
|
|
|
}, |
73
|
|
|
|
|
|
|
properties => [ Glib::ParamSpec->object |
74
|
|
|
|
|
|
|
('model', |
75
|
|
|
|
|
|
|
'Model object', |
76
|
|
|
|
|
|
|
'TreeModel giving the items to display.', |
77
|
|
|
|
|
|
|
'Gtk2::TreeModel', |
78
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
Glib::ParamSpec->boolean |
81
|
|
|
|
|
|
|
('run', |
82
|
|
|
|
|
|
|
'Run ticker', |
83
|
|
|
|
|
|
|
'Whether to run the ticker, ie. scroll across.', |
84
|
|
|
|
|
|
|
1, # default yes |
85
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
Glib::ParamSpec->double |
88
|
|
|
|
|
|
|
('speed', |
89
|
|
|
|
|
|
|
'Speed', |
90
|
|
|
|
|
|
|
'Speed to move the items across, in pixels per second.', |
91
|
|
|
|
|
|
|
0, POSIX::DBL_MAX(), |
92
|
|
|
|
|
|
|
DEFAULT_SPEED, |
93
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
Glib::ParamSpec->double |
96
|
|
|
|
|
|
|
('frame-rate', |
97
|
|
|
|
|
|
|
'Frame rate', |
98
|
|
|
|
|
|
|
'How many times per second to move for scrolling.', |
99
|
|
|
|
|
|
|
0, POSIX::DBL_MAX(), |
100
|
|
|
|
|
|
|
DEFAULT_FRAME_RATE, |
101
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
Glib::ParamSpec->boolean |
104
|
|
|
|
|
|
|
('fixed-height-mode', |
105
|
|
|
|
|
|
|
'Fixed height mode', |
106
|
|
|
|
|
|
|
'Assume all cells have the same desired height.', |
107
|
|
|
|
|
|
|
0, # default no |
108
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Glib::ParamSpec->enum |
111
|
|
|
|
|
|
|
('orientation', |
112
|
|
|
|
|
|
|
'Orientation', # dgettext('gtk20-properties') |
113
|
|
|
|
|
|
|
'Horizontal or vertical display and scrolling.', |
114
|
|
|
|
|
|
|
'Gtk2::Orientation', |
115
|
|
|
|
|
|
|
'horizontal', |
116
|
|
|
|
|
|
|
Glib::G_PARAM_READWRITE), |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
]; |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# The private per-object fields are: |
121
|
|
|
|
|
|
|
# |
122
|
|
|
|
|
|
|
# vertical 0 or 1 |
123
|
|
|
|
|
|
|
# 0 when horizontal, 1 when vertical. Presented through GET_PROPERTY |
124
|
|
|
|
|
|
|
# and SET_PROPERTY as Gtk2::Orientation 'horizontal' and 'vertical', but |
125
|
|
|
|
|
|
|
# internally the number allows computed indexes instead of conditionals. |
126
|
|
|
|
|
|
|
# |
127
|
|
|
|
|
|
|
# pixmap Gtk2::Gdk::Pixmap or undef |
128
|
|
|
|
|
|
|
# Established in _pixmap(), undef until then. |
129
|
|
|
|
|
|
|
# |
130
|
|
|
|
|
|
|
# pixmap_size |
131
|
|
|
|
|
|
|
# The width of pixmap when horizontal, or height when vertical. This is |
132
|
|
|
|
|
|
|
# established by _pixmap_desired_size() and thus may be set before |
133
|
|
|
|
|
|
|
# pixmap is actually created. |
134
|
|
|
|
|
|
|
# |
135
|
|
|
|
|
|
|
# row_widths hashref { $index => $width } |
136
|
|
|
|
|
|
|
# $index is an integer row number. $width is the total width of all the |
137
|
|
|
|
|
|
|
# renderers' drawing of that row. |
138
|
|
|
|
|
|
|
# |
139
|
|
|
|
|
|
|
# This is a hash instead of an array so as to keep widths for roughly |
140
|
|
|
|
|
|
|
# just the rows displayed, which may be a good thing on a big model. |
141
|
|
|
|
|
|
|
# Widths are discarded when _pixmap_shift drops rows off the left edge, |
142
|
|
|
|
|
|
|
# and when _pixmap_empty purges the whole pixmap. Widths are stored by |
143
|
|
|
|
|
|
|
# _pixmap_extend, so the displayed rows are present, ready for |
144
|
|
|
|
|
|
|
# _normalize() to use contemplating an increment of want_index. |
145
|
|
|
|
|
|
|
# |
146
|
|
|
|
|
|
|
# Widths are also saved by normalize actions like a big scroll_pixels() |
147
|
|
|
|
|
|
|
# or a get_path_at_pos() of an off-screen position. Those widths end up |
148
|
|
|
|
|
|
|
# remaining until a full redraw or until shifting passes them by. Which |
149
|
|
|
|
|
|
|
# is probably no bad thing if one off-screen position calculation is |
150
|
|
|
|
|
|
|
# reasonably likely to be followed by another. Might be worth thinking |
151
|
|
|
|
|
|
|
# more about that though. |
152
|
|
|
|
|
|
|
# |
153
|
|
|
|
|
|
|
# The widths of drawn rows are implicitly in drawn_array as the |
154
|
|
|
|
|
|
|
# difference between successive $x values, so having them in |
155
|
|
|
|
|
|
|
# 'row_widths' is a bit of duplication. If _normalize() looked at |
156
|
|
|
|
|
|
|
# drawn_array the drawn rows could be omitted, but almost certainly the |
157
|
|
|
|
|
|
|
# code size would outweigh the data space. |
158
|
|
|
|
|
|
|
# |
159
|
|
|
|
|
|
|
# drawn_array arrayref [ $index, $x, $index, $x, ... ] |
160
|
|
|
|
|
|
|
# Each $index is an integer row number. Each $x is the position in |
161
|
|
|
|
|
|
|
# 'pixmap' where row $index has been drawn. There's two things helped |
162
|
|
|
|
|
|
|
# by saveing all drawn positions, |
163
|
|
|
|
|
|
|
# |
164
|
|
|
|
|
|
|
# 1. _pixmap_shift() is easier. It can decrement each $x by the shift |
165
|
|
|
|
|
|
|
# amount and look for the last $x <= 0 as the drawn rows retained. |
166
|
|
|
|
|
|
|
# When the last row has been chopped off at the right edge (which is |
167
|
|
|
|
|
|
|
# almost always) the second last position is immediately available to |
168
|
|
|
|
|
|
|
# become pixmap_end_x, and the last index is pixmap_end_index to |
169
|
|
|
|
|
|
|
# extend from. |
170
|
|
|
|
|
|
|
# |
171
|
|
|
|
|
|
|
# If all positions weren't recorded then _pixmap_shift() would have |
172
|
|
|
|
|
|
|
# to reconstruct them to find that $x <= 0 point and the second last |
173
|
|
|
|
|
|
|
# row pos, by adding up row widths. |
174
|
|
|
|
|
|
|
# |
175
|
|
|
|
|
|
|
# 2. _pixmap_find_want() can contemplate two drawn copies of a given |
176
|
|
|
|
|
|
|
# row. There might be one at the start which starts a bit off screen |
177
|
|
|
|
|
|
|
# at the left, and another later in the pixmap. When want_x allows |
178
|
|
|
|
|
|
|
# the first to be used it makes best use of the current pixmap |
179
|
|
|
|
|
|
|
# contents. If not then the second is a fallback and if it's too far |
180
|
|
|
|
|
|
|
# to the right then might still be usable if _pixmap_shift() moves it |
181
|
|
|
|
|
|
|
# down. |
182
|
|
|
|
|
|
|
# |
183
|
|
|
|
|
|
|
# Point 2 might be covered by retaining two positions for each row: its |
184
|
|
|
|
|
|
|
# leftmost and then second leftmost (if any). _pixmap_shift() would |
185
|
|
|
|
|
|
|
# still be tricky though, if it tried to find a new second leftmost for |
186
|
|
|
|
|
|
|
# those rows whose leftmost had gone off-screen. |
187
|
|
|
|
|
|
|
# |
188
|
|
|
|
|
|
|
# Rows of zero width are still entered in drawn_array. This ensures |
189
|
|
|
|
|
|
|
# _do_row_changed, _do_row_deleted, etc, notice that those rows are |
190
|
|
|
|
|
|
|
# on-screen. It's possible want_index is a zero width row if an |
191
|
|
|
|
|
|
|
# explicit scroll_to_start has gone there (or a hypothetical |
192
|
|
|
|
|
|
|
# scroll_to_iter or something like that). _normalize() normally doesn't |
193
|
|
|
|
|
|
|
# leave want_index on a zero width when moving though. |
194
|
|
|
|
|
|
|
# |
195
|
|
|
|
|
|
|
# want_x, want_index integers |
196
|
|
|
|
|
|
|
# want_index is the desired row number (counting from 0) to be shown at |
197
|
|
|
|
|
|
|
# the start of the ticker window. want_x is an x position where the |
198
|
|
|
|
|
|
|
# left edge of the want_index row should start. want_x is zero or |
199
|
|
|
|
|
|
|
# negative. Negative means the want_index item is partly off the left |
200
|
|
|
|
|
|
|
# edge. |
201
|
|
|
|
|
|
|
# |
202
|
|
|
|
|
|
|
# Scrolling will soon make want_x a larger negative than the width of |
203
|
|
|
|
|
|
|
# the want_index row. Expose (using _normalize()) looks for that and |
204
|
|
|
|
|
|
|
# moves up by incrementing want_index and adding the skipped row width |
205
|
|
|
|
|
|
|
# to want_x. It can go across multiple rows that way if necessary. |
206
|
|
|
|
|
|
|
# |
207
|
|
|
|
|
|
|
# Scrolling backwards can make want_x go positive. _normalize_want() |
208
|
|
|
|
|
|
|
# and _normalize() again adjust, this time by decrementing want_index to |
209
|
|
|
|
|
|
|
# a preceding row and subtracting that width from want_x, working back |
210
|
|
|
|
|
|
|
# to find what row should be at or just before the left edge. |
211
|
|
|
|
|
|
|
# |
212
|
|
|
|
|
|
|
# pixmap_end_x |
213
|
|
|
|
|
|
|
# The x just after the endmost drawn part of the pixmap. pixmap_end_x |
214
|
|
|
|
|
|
|
# is truncated to pixmap_size when full or when the model is empty or |
215
|
|
|
|
|
|
|
# all zero width rows. |
216
|
|
|
|
|
|
|
# |
217
|
|
|
|
|
|
|
# Until pixmap_end_x is faked to pixmap_size it's equal to the last |
218
|
|
|
|
|
|
|
# drawn_array entry plus that entry's row_width. But the fakery to |
219
|
|
|
|
|
|
|
# pixmap_size means it's easier to maintain pixmap_end_x separately than |
220
|
|
|
|
|
|
|
# to build from drawn_array each time. |
221
|
|
|
|
|
|
|
# |
222
|
|
|
|
|
|
|
# The pixmap starts with only as much content as needed for expose to |
223
|
|
|
|
|
|
|
# show the expose region the want_index/want_x position. As |
224
|
|
|
|
|
|
|
# want_x,want_index advance _pixmap_extend() draws more content at |
225
|
|
|
|
|
|
|
# pixmap_end_x. |
226
|
|
|
|
|
|
|
# |
227
|
|
|
|
|
|
|
# The "undrawn" area from pixmap_end_x onwards is always cleared to the |
228
|
|
|
|
|
|
|
# background colour (in _pixmap_redraw() and _pixmap_shift()), so |
229
|
|
|
|
|
|
|
# _pixmap_extend() can just draw. Some of that area might never be used |
230
|
|
|
|
|
|
|
# but the idea is to do a single big XDrawRectangle instead of several |
231
|
|
|
|
|
|
|
# small ones. |
232
|
|
|
|
|
|
|
# |
233
|
|
|
|
|
|
|
# visibility_state Gtk2::Gdk::VisibilityState enum string or 'initial' |
234
|
|
|
|
|
|
|
# This is maintained from the 'visiblity-notify-event' handler. If |
235
|
|
|
|
|
|
|
# 'fully-obscured' then the scroll timer is stopped, to save a bit of |
236
|
|
|
|
|
|
|
# work when nothing can be seen. |
237
|
|
|
|
|
|
|
# |
238
|
|
|
|
|
|
|
# drag_xy arrayref [ $root_x, $root_y ] |
239
|
|
|
|
|
|
|
# During a drag this is the last position of the mouse, in root window |
240
|
|
|
|
|
|
|
# coordinates. When not in drag 'drag_xy' is not in $self at all. The |
241
|
|
|
|
|
|
|
# timer is stopped while drag_xy is set. Only one of the two x or y are |
242
|
|
|
|
|
|
|
# used, according to 'vertical', but storing not much extra and it |
243
|
|
|
|
|
|
|
# smoothly handles the slightly freaky case of a change of orientation |
244
|
|
|
|
|
|
|
# in the middle of a drag. |
245
|
|
|
|
|
|
|
# |
246
|
|
|
|
|
|
|
# model_empty boolean |
247
|
|
|
|
|
|
|
# True when we believe $self->{'model'} is empty. Initialized in |
248
|
|
|
|
|
|
|
# SET_PROPERTY when the model is first set, then kept up to date in |
249
|
|
|
|
|
|
|
# _do_row_inserted() and _do_row_deleted(). |
250
|
|
|
|
|
|
|
# |
251
|
|
|
|
|
|
|
# The aim of this is to give _do_row_inserted() a way to be sure when |
252
|
|
|
|
|
|
|
# the model transitions from empty to non-empty, which provokes a resize |
253
|
|
|
|
|
|
|
# and a possible timer restart. |
254
|
|
|
|
|
|
|
# |
255
|
|
|
|
|
|
|
# Testing for model length == 1 in _do_row_inserted() would be very |
256
|
|
|
|
|
|
|
# nearly enough, but not perfect. If an earlier connected row-inserted |
257
|
|
|
|
|
|
|
# handler inserts yet another row in response to the first insertion |
258
|
|
|
|
|
|
|
# then by the time _do_row_inserted() runs it sees length==2. This |
259
|
|
|
|
|
|
|
# would be pretty unusual, and probably needs 'iters-persist' on the |
260
|
|
|
|
|
|
|
# model for the first iter to remain valid past the extra model change, |
261
|
|
|
|
|
|
|
# but it's not completely outrageous. |
262
|
|
|
|
|
|
|
# |
263
|
|
|
|
|
|
|
# In RtoL mode all the x positions are measured from the right edge of the |
264
|
|
|
|
|
|
|
# window or pixmap instead. Only the expose and the cell renderer drawing |
265
|
|
|
|
|
|
|
# must mirror those RtoL logical positions into LtoR screen coordinates. |
266
|
|
|
|
|
|
|
# |
267
|
|
|
|
|
|
|
# In vertical orientation the "x" values are in fact y positions and the |
268
|
|
|
|
|
|
|
# "width"s are in fact heights. Stand by for a search and replace to |
269
|
|
|
|
|
|
|
# something neutral like "p" and "size" or whatever :-). |
270
|
|
|
|
|
|
|
# |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sub INIT_INSTANCE { |
273
|
|
|
|
|
|
|
my ($self) = @_; |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# the offscreen 'pixmap' already works as a form of double buffering, no |
276
|
|
|
|
|
|
|
# need for DBE |
277
|
|
|
|
|
|
|
$self->set_double_buffered (0); |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
$self->{'want_index'} = 0; |
280
|
|
|
|
|
|
|
$self->{'want_x'} = 0; |
281
|
|
|
|
|
|
|
$self->{'row_widths'} = {}; |
282
|
|
|
|
|
|
|
$self->{'drawn_array'} = []; |
283
|
|
|
|
|
|
|
$self->{'pixmap_end_x'} = 0; |
284
|
|
|
|
|
|
|
$self->{'visibility_state'} = 'initial'; |
285
|
|
|
|
|
|
|
$self->{'run'} = 1; # default yes |
286
|
|
|
|
|
|
|
$self->{'frame_rate'} = DEFAULT_FRAME_RATE; |
287
|
|
|
|
|
|
|
$self->{'speed'} = DEFAULT_SPEED; |
288
|
|
|
|
|
|
|
$self->{'vertical'} = 0; |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
$self->add_events (['visibility-notify-mask', |
291
|
|
|
|
|
|
|
'button-press-mask', |
292
|
|
|
|
|
|
|
'button-motion-mask', |
293
|
|
|
|
|
|
|
'button-release-mask']); |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
sub GET_PROPERTY { |
297
|
|
|
|
|
|
|
my ($self, $pspec) = @_; |
298
|
|
|
|
|
|
|
my $pname = $pspec->get_name; |
299
|
|
|
|
|
|
|
if ($pname eq 'orientation') { |
300
|
|
|
|
|
|
|
return ($self->{'vertical'} ? 'vertical' : 'horizontal'); |
301
|
|
|
|
|
|
|
} else { |
302
|
|
|
|
|
|
|
return $self->{$pname}; |
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
sub SET_PROPERTY { |
307
|
|
|
|
|
|
|
my ($self, $pspec, $newval) = @_; |
308
|
|
|
|
|
|
|
my $pname = $pspec->get_name; |
309
|
|
|
|
|
|
|
my $oldval = $self->{$pname}; |
310
|
|
|
|
|
|
|
### SET_PROPERTY: $pname, $newval |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
if ($pname eq 'orientation') { |
313
|
|
|
|
|
|
|
$oldval = $self->{'vertical'}; |
314
|
|
|
|
|
|
|
$self->{'vertical'} = $newval = ($newval eq 'horizontal' ? 0 : 1); |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
} else { |
317
|
|
|
|
|
|
|
$self->{$pname} = $newval; |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
if ($pname eq 'model') { |
320
|
|
|
|
|
|
|
if (($oldval||0) == ($newval||0)) { |
321
|
|
|
|
|
|
|
# no change, avoid resize, redraw, etc |
322
|
|
|
|
|
|
|
return; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
my $model = $newval; |
325
|
|
|
|
|
|
|
$self->{'model_empty'} = ! ($model && $model->get_iter_first); |
326
|
|
|
|
|
|
|
$self->{'model_ids'} = $model && do { |
327
|
|
|
|
|
|
|
Scalar::Util::weaken (my $weak_self = $self); |
328
|
|
|
|
|
|
|
my $ref_weak_self = \$weak_self; |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
require Glib::Ex::SignalIds; |
331
|
|
|
|
|
|
|
Glib::Ex::SignalIds->new |
332
|
|
|
|
|
|
|
($model, |
333
|
|
|
|
|
|
|
$model->signal_connect (row_changed => \&_do_row_changed, |
334
|
|
|
|
|
|
|
$ref_weak_self), |
335
|
|
|
|
|
|
|
$model->signal_connect (row_inserted => \&_do_row_inserted, |
336
|
|
|
|
|
|
|
$ref_weak_self), |
337
|
|
|
|
|
|
|
$model->signal_connect (row_deleted => \&_do_row_deleted, |
338
|
|
|
|
|
|
|
$ref_weak_self), |
339
|
|
|
|
|
|
|
$model->signal_connect (rows_reordered => \&_do_rows_reordered, |
340
|
|
|
|
|
|
|
$ref_weak_self)) |
341
|
|
|
|
|
|
|
}; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
# updates ... |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# 'fixed_height_mode' turned to false provokes a resize, so as to look at |
348
|
|
|
|
|
|
|
# all the rows now, not just the first. But don't resize when turning |
349
|
|
|
|
|
|
|
# fixed_height_mode to true since the size we have is based on all rows |
350
|
|
|
|
|
|
|
# and if the first row is truely representative then its size is the same |
351
|
|
|
|
|
|
|
# as already in use. |
352
|
|
|
|
|
|
|
# |
353
|
|
|
|
|
|
|
if ($pname eq 'model' |
354
|
|
|
|
|
|
|
|| ($pname eq 'orientation' && $oldval != $newval)) { |
355
|
|
|
|
|
|
|
### model or orientation change |
356
|
|
|
|
|
|
|
%{$self->{'row_widths'}} = (); |
357
|
|
|
|
|
|
|
$self->queue_resize; |
358
|
|
|
|
|
|
|
_pixmap_queue_draw ($self); # zap pixmap contents |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
if ($pname eq 'fixed_height_mode' && $oldval && ! $newval) { |
362
|
|
|
|
|
|
|
$self->queue_resize; |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
if ($pname eq 'model' || $pname eq 'run' || $pname eq 'frame_rate') { |
366
|
|
|
|
|
|
|
if ($pname eq 'frame_rate') { |
367
|
|
|
|
|
|
|
# Discard old timer ready for new rate. Might like to carry over the |
368
|
|
|
|
|
|
|
# elapsed period in the old one, but it should be short enough not to |
369
|
|
|
|
|
|
|
# matter, and Glib doesn't seem to have much to help such a |
370
|
|
|
|
|
|
|
# calculation. |
371
|
|
|
|
|
|
|
delete $self->{'timer'}; |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
_update_timer ($self); |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
379
|
|
|
|
|
|
|
# size desired and allocated |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
# 'size-request' class closure |
382
|
|
|
|
|
|
|
sub _do_size_request { |
383
|
|
|
|
|
|
|
my ($self, $req) = @_; |
384
|
|
|
|
|
|
|
### TickerView _do_size_request() |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
$req->width (0); |
387
|
|
|
|
|
|
|
$req->height (0); |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
my $model = $self->{'model'} || return; # no size if no model |
390
|
|
|
|
|
|
|
my @cells = $self->GET_CELLS; |
391
|
|
|
|
|
|
|
@cells || return; # no size if no cells |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
my $sizefield = 3 - $self->{'vertical'}; # width vert, height horiz |
394
|
|
|
|
|
|
|
my $want_size = 0; |
395
|
|
|
|
|
|
|
for (my $iter = $model->get_iter_first; |
396
|
|
|
|
|
|
|
$iter; |
397
|
|
|
|
|
|
|
$iter = $model->iter_next ($iter)) { |
398
|
|
|
|
|
|
|
$self->_set_cell_data ($iter); |
399
|
|
|
|
|
|
|
foreach my $cell (@cells) { |
400
|
|
|
|
|
|
|
$want_size = max ($want_size, |
401
|
|
|
|
|
|
|
($cell->get_size($self,undef))[$sizefield]); |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
if ($self->{'fixed_height_mode'}) { |
404
|
|
|
|
|
|
|
### one row only for fixed-height-mode |
405
|
|
|
|
|
|
|
last; |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
if ($sizefield == 3) { |
410
|
|
|
|
|
|
|
$req->height ($want_size); |
411
|
|
|
|
|
|
|
} else { |
412
|
|
|
|
|
|
|
$req->width ($want_size); |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
### decide size: $req->width."x".$req->height |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
# 'size_allocate' class closure |
418
|
|
|
|
|
|
|
# |
419
|
|
|
|
|
|
|
# This is also reached for cell renderer and attribute changes through |
420
|
|
|
|
|
|
|
# $self->queue_resize in CellLayout::Base. |
421
|
|
|
|
|
|
|
# |
422
|
|
|
|
|
|
|
# For a move without a resize the pixmap at its current size could be |
423
|
|
|
|
|
|
|
# retained. Probably moves alone won't occur often enough to make that |
424
|
|
|
|
|
|
|
# worth worrying about. |
425
|
|
|
|
|
|
|
# |
426
|
|
|
|
|
|
|
# Crib: no queue_draw() here, since the default redraw_on_alloc means that's |
427
|
|
|
|
|
|
|
# done automatically (in gtk_widget_size_allocate()). |
428
|
|
|
|
|
|
|
# |
429
|
|
|
|
|
|
|
sub _do_size_allocate { |
430
|
|
|
|
|
|
|
my ($self, $alloc) = @_; |
431
|
|
|
|
|
|
|
### TickerView _do_size_allocate() |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
$self->signal_chain_from_overridden ($alloc); |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
if (my $pixmap = $self->{'pixmap'}) { |
436
|
|
|
|
|
|
|
my ($want_width, $want_height) = _pixmap_desired_size ($self, $alloc); |
437
|
|
|
|
|
|
|
my ($got_width, $got_height) = $pixmap->get_size; |
438
|
|
|
|
|
|
|
if ($want_width != $got_width || $want_height != $got_height) { |
439
|
|
|
|
|
|
|
### want new pixmap size |
440
|
|
|
|
|
|
|
$self->{'pixmap'} = undef; |
441
|
|
|
|
|
|
|
_pixmap_queue_draw ($self); |
442
|
|
|
|
|
|
|
return; |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
#----------------------------------------------------------------------------- |
449
|
|
|
|
|
|
|
# expose and pixmap |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
# 'expose-event' class closure, getting Gtk2::Gdk::Event::Expose |
452
|
|
|
|
|
|
|
sub _do_expose_event { |
453
|
|
|
|
|
|
|
my ($self, $event) = @_; |
454
|
|
|
|
|
|
|
if (DEBUG >= 2) { |
455
|
|
|
|
|
|
|
my $expose_size = ($self->{'vertical'} |
456
|
|
|
|
|
|
|
? $event->area->y + $event->area->height |
457
|
|
|
|
|
|
|
: $event->area->x + $event->area->width); |
458
|
|
|
|
|
|
|
print "TickerView _do_expose_event count=",$event->count, |
459
|
|
|
|
|
|
|
" pixmap_redraw=",($self->{'pixmap_redraw'}?"yes":"no"), |
460
|
|
|
|
|
|
|
" expose_size=$expose_size\n"; |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
my $expose_size = do { my $expose_rect = $event->area; |
464
|
|
|
|
|
|
|
($self->{'vertical'} |
465
|
|
|
|
|
|
|
? $expose_rect->y + $expose_rect->height |
466
|
|
|
|
|
|
|
: $expose_rect->x + $expose_rect->width) }; |
467
|
|
|
|
|
|
|
my $pixmap = _pixmap($self); |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
my $x; |
470
|
|
|
|
|
|
|
if ($self->{'pixmap_redraw'} |
471
|
|
|
|
|
|
|
|| ! defined ($x = _pixmap_find_want ($self, $expose_size))) { |
472
|
|
|
|
|
|
|
# want_index/want_x isn't in the pixmap at all |
473
|
|
|
|
|
|
|
_pixmap_empty ($self); |
474
|
|
|
|
|
|
|
$x = 0; |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
} elsif ($x + $expose_size > $self->{'pixmap_size'}) { |
477
|
|
|
|
|
|
|
# want_index/want_x is in the pixmap, but there's not enough space after |
478
|
|
|
|
|
|
|
# it for the $expose_size |
479
|
|
|
|
|
|
|
if (DEBUG >= 2) { print "expose found $x but +$expose_size =", |
480
|
|
|
|
|
|
|
$x + $expose_size, |
481
|
|
|
|
|
|
|
" is past $self->{'pixmap_size'}, so shift\n"; } |
482
|
|
|
|
|
|
|
_pixmap_shift ($self, $x); |
483
|
|
|
|
|
|
|
$x = 0; |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
_pixmap_extend ($self, $x + $expose_size); |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
if ($self->get_direction eq 'rtl') { |
488
|
|
|
|
|
|
|
# width when horiz, height when vert |
489
|
|
|
|
|
|
|
my $win_size = ($self->allocation->values)[2 + $self->{'vertical'}]; |
490
|
|
|
|
|
|
|
$x = $self->{'pixmap_size'} - 1 - $win_size - $x; |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
my $win = $self->window; |
493
|
|
|
|
|
|
|
my $gc = $self->get_style->black_gc; # any gc for an XCopyArea |
494
|
|
|
|
|
|
|
$gc->set_clip_region ($event->region); |
495
|
|
|
|
|
|
|
$win->draw_drawable ($gc, $pixmap, |
496
|
|
|
|
|
|
|
($self->{'vertical'} ? (0,$x) : ($x,0)), # src |
497
|
|
|
|
|
|
|
0,0, # dst |
498
|
|
|
|
|
|
|
$win->get_size); |
499
|
|
|
|
|
|
|
$gc->set_clip_region (undef); |
500
|
|
|
|
|
|
|
return 0; # Gtk2::EVENT_PROPAGATE |
501
|
|
|
|
|
|
|
} |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
sub _pixmap_find_want { |
504
|
|
|
|
|
|
|
my ($self, $expose_size) = @_; |
505
|
|
|
|
|
|
|
if (DEBUG >= 2) { print " _pixmap_find_want", |
506
|
|
|
|
|
|
|
" want_index=$self->{'want_index'}", |
507
|
|
|
|
|
|
|
" want_x=$self->{'want_x'}\n"; } |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
# the usual case here is _normalize() finding want_index still within its |
510
|
|
|
|
|
|
|
# row width and the previously determined drawn_want_at in drawn_array is |
511
|
|
|
|
|
|
|
# still wholely in a drawn portion of the pixmap |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
my ($want_x, $want_index) |
514
|
|
|
|
|
|
|
= _normalize ($self, $self->{'want_x'}, $self->{'want_index'}); |
515
|
|
|
|
|
|
|
if (! defined $want_x) { |
516
|
|
|
|
|
|
|
# no model, or model empty, or all rows zero width |
517
|
|
|
|
|
|
|
return ($self->{'want_x'} = 0); |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
$self->{'want_x'} = $want_x; |
520
|
|
|
|
|
|
|
$self->{'want_index'} = $want_index; |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
$want_x = POSIX::floor ($want_x); |
523
|
|
|
|
|
|
|
my $drawn = $self->{'drawn_array'}; |
524
|
|
|
|
|
|
|
my ($i, $x); |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
# see if the cached 'drawn_want_at' is still the wanted index and in range |
527
|
|
|
|
|
|
|
if (defined ($i = $self->{'drawn_want_at'}) |
528
|
|
|
|
|
|
|
&& defined $drawn->[$i] |
529
|
|
|
|
|
|
|
&& $drawn->[$i] == $want_index |
530
|
|
|
|
|
|
|
&& ($x = $drawn->[$i+1] - $want_x) >= 0 |
531
|
|
|
|
|
|
|
&& $x + $expose_size <= $self->{'pixmap_size'}) { |
532
|
|
|
|
|
|
|
if (DEBUG >= 2) { print " drawn_want_at still good\n"; } |
533
|
|
|
|
|
|
|
return $x; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
if (DEBUG >= 2) { print " seeking leftmost $want_index\n"; } |
537
|
|
|
|
|
|
|
for ($i = 0; $i < @$drawn; $i+=2) { |
538
|
|
|
|
|
|
|
if ($drawn->[$i] == $want_index |
539
|
|
|
|
|
|
|
&& (($x = $drawn->[$i+1] - $want_x) >= 0)) { |
540
|
|
|
|
|
|
|
$self->{'drawn_want_at'} = $i; |
541
|
|
|
|
|
|
|
return $x; |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
if (DEBUG >= 2) { local $,=' '; print " not found in",@$drawn,"\n"; } |
545
|
|
|
|
|
|
|
return undef; |
546
|
|
|
|
|
|
|
} |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
sub _pixmap_empty { |
549
|
|
|
|
|
|
|
my ($self) = @_; |
550
|
|
|
|
|
|
|
if (DEBUG) { print "_pixmap_empty to", |
551
|
|
|
|
|
|
|
" want_index=$self->{'want_index'},", |
552
|
|
|
|
|
|
|
"want_x=$self->{'want_x'}\n"; } |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
$self->{'pixmap_redraw'} = 0; |
555
|
|
|
|
|
|
|
my $pixmap = _pixmap($self); |
556
|
|
|
|
|
|
|
my ($pixmap_width, $pixmap_height) = $pixmap->get_size; |
557
|
|
|
|
|
|
|
my $gc = $self->get_style->bg_gc ($self->state); |
558
|
|
|
|
|
|
|
$pixmap->draw_rectangle ($gc, 1, 0,0, $pixmap_width,$pixmap_height); |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
@{$self->{'drawn_array'}} = (); |
561
|
|
|
|
|
|
|
$self->{'drawn_want_at'} = undef; |
562
|
|
|
|
|
|
|
%{$self->{'row_widths'}} = (); # prune |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
$self->{'pixmap_end_x'} = POSIX::floor ($self->{'want_x'}); |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
sub _pixmap_shift { |
568
|
|
|
|
|
|
|
my ($self, $offset) = @_; |
569
|
|
|
|
|
|
|
if (DEBUG >= 2) { |
570
|
|
|
|
|
|
|
print "_pixmap_shift offset=$offset, from", |
571
|
|
|
|
|
|
|
" pixmap_end_x=",(defined $self->{'pixmap_end_x'} ? $self->{'pixmap_end_x'} : 'undef'), |
572
|
|
|
|
|
|
|
"\n"; |
573
|
|
|
|
|
|
|
} |
574
|
|
|
|
|
|
|
my $pixmap_size = $self->{'pixmap_size'}; |
575
|
|
|
|
|
|
|
my $drawn = $self->{'drawn_array'}; |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
# if the rightmost drawn goes past the end of the pixmap then discard it |
578
|
|
|
|
|
|
|
if (@$drawn |
579
|
|
|
|
|
|
|
&& $drawn->[-1] + _row_width($self,$drawn->[-2]) > $pixmap_size) { |
580
|
|
|
|
|
|
|
if (DEBUG >= 2) { print " last index=$drawn->[-2] past end, drop it\n"; } |
581
|
|
|
|
|
|
|
$self->{'pixmap_end_x'} = pop @$drawn; |
582
|
|
|
|
|
|
|
pop @$drawn; # index |
583
|
|
|
|
|
|
|
if (! @$drawn) { goto \&_pixmap_empty; } |
584
|
|
|
|
|
|
|
} |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
my %prune_row_widths; # keys are the indexes to be discarded |
587
|
|
|
|
|
|
|
my $last_nonpositive = 0; |
588
|
|
|
|
|
|
|
for (my $i = 0; $i < @$drawn; $i+=2) { |
589
|
|
|
|
|
|
|
if (($drawn->[$i+1] -= $offset) <= 0) { |
590
|
|
|
|
|
|
|
$last_nonpositive = $i; |
591
|
|
|
|
|
|
|
$prune_row_widths{$drawn->[$i]} = 1; |
592
|
|
|
|
|
|
|
} else { |
593
|
|
|
|
|
|
|
delete $prune_row_widths{$drawn->[$i]}; |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
my $end_x = ($self->{'pixmap_end_x'} -= $offset); |
597
|
|
|
|
|
|
|
splice @$drawn, 0, $last_nonpositive; |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
# row_widths for rows shifted off at the left are dropped, unless they |
600
|
|
|
|
|
|
|
# also occur later in the drawn contents |
601
|
|
|
|
|
|
|
delete $prune_row_widths{$drawn->[0]}; |
602
|
|
|
|
|
|
|
delete @{$self->{'row_widths'}} {keys %prune_row_widths}; # hash slice |
603
|
|
|
|
|
|
|
if (DEBUG >= 2) { |
604
|
|
|
|
|
|
|
local $,=' '; print " prune row widths:", keys %prune_row_widths,"\n"; |
605
|
|
|
|
|
|
|
} |
606
|
|
|
|
|
|
|
# CHECK-ME: probably not supposed to shift down to nothing ... |
607
|
|
|
|
|
|
|
if (! @$drawn) { goto \&_pixmap_empty; } |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
if (DEBUG >= 2) { local $,=' '; print " now drawn",@$drawn,"\n"; } |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
my $pixmap = $self->{'pixmap'}; |
612
|
|
|
|
|
|
|
my ($pixmap_width, $pixmap_height) = $pixmap->get_size; |
613
|
|
|
|
|
|
|
my $gc = $self->get_style->bg_gc ($self->state); |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
# $end_x shouldn't be negative, but apply clamp $copy_size just in case. |
616
|
|
|
|
|
|
|
# Won't have $offset==0, thus won't have $src_x==$dst_x, so no sort circuits. |
617
|
|
|
|
|
|
|
# |
618
|
|
|
|
|
|
|
my $copy_size = max ($end_x, 0); |
619
|
|
|
|
|
|
|
my $clear_size = $pixmap_size - $copy_size; |
620
|
|
|
|
|
|
|
my ($src_x, $dst_x, $clear_x); |
621
|
|
|
|
|
|
|
if ($self->get_direction eq 'ltr') { |
622
|
|
|
|
|
|
|
# $pixmap_size |
623
|
|
|
|
|
|
|
# +------+----------------+----+ |
624
|
|
|
|
|
|
|
# | | $copy_size | | |
625
|
|
|
|
|
|
|
# +------+----------------+----+ |
626
|
|
|
|
|
|
|
# $offset $end_x --> measuring rightwards |
627
|
|
|
|
|
|
|
# / / |
628
|
|
|
|
|
|
|
# +----------------+-----------+ |
629
|
|
|
|
|
|
|
# | $copy_size |$clear_size| |
630
|
|
|
|
|
|
|
# +----------------+-----------+ |
631
|
|
|
|
|
|
|
# |
632
|
|
|
|
|
|
|
$dst_x = 0; |
633
|
|
|
|
|
|
|
$src_x = $offset; |
634
|
|
|
|
|
|
|
$clear_x = $copy_size; |
635
|
|
|
|
|
|
|
} else { |
636
|
|
|
|
|
|
|
# $pixmap_size |
637
|
|
|
|
|
|
|
# +----+----------------+------+ |
638
|
|
|
|
|
|
|
# | | $copy_size | | |
639
|
|
|
|
|
|
|
# +----+----------------+------+ |
640
|
|
|
|
|
|
|
# $end_x $offset <-- measuring leftwards |
641
|
|
|
|
|
|
|
# \ \ |
642
|
|
|
|
|
|
|
# +-----------+----------------+ |
643
|
|
|
|
|
|
|
# |$clear_size| $copy_size | |
644
|
|
|
|
|
|
|
# +-----------+----------------+ |
645
|
|
|
|
|
|
|
# |
646
|
|
|
|
|
|
|
$dst_x = $clear_size; |
647
|
|
|
|
|
|
|
$src_x = $dst_x - $offset; |
648
|
|
|
|
|
|
|
$clear_x = 0; |
649
|
|
|
|
|
|
|
} |
650
|
|
|
|
|
|
|
if (DEBUG >= 2) { print " copy $src_x to $dst_x size=$copy_size\n"; |
651
|
|
|
|
|
|
|
print " clear $clear_x size=$clear_size\n"; } |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
if ($self->{'vertical'}) { |
654
|
|
|
|
|
|
|
$pixmap->draw_drawable ($gc, $pixmap, |
655
|
|
|
|
|
|
|
0,$src_x, # src |
656
|
|
|
|
|
|
|
0,$dst_x, # dst |
657
|
|
|
|
|
|
|
$pixmap_width, $copy_size); # width,height |
658
|
|
|
|
|
|
|
# clear the remainder |
659
|
|
|
|
|
|
|
$pixmap->draw_rectangle ($gc, 1, |
660
|
|
|
|
|
|
|
0,$clear_x, |
661
|
|
|
|
|
|
|
$pixmap_width, $clear_size); |
662
|
|
|
|
|
|
|
} else { |
663
|
|
|
|
|
|
|
# horizontal |
664
|
|
|
|
|
|
|
$pixmap->draw_drawable ($gc, $pixmap, |
665
|
|
|
|
|
|
|
$src_x,0, # src |
666
|
|
|
|
|
|
|
$dst_x,0, # dst |
667
|
|
|
|
|
|
|
$copy_size, $pixmap_height); # width,height |
668
|
|
|
|
|
|
|
# clear the remainder |
669
|
|
|
|
|
|
|
$pixmap->draw_rectangle ($gc, 1, |
670
|
|
|
|
|
|
|
$clear_x,0, |
671
|
|
|
|
|
|
|
$clear_size, $pixmap_height); |
672
|
|
|
|
|
|
|
} |
673
|
|
|
|
|
|
|
} |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
# draw more at 'pixmap_end_x' to ensure it's not less than $target_x |
676
|
|
|
|
|
|
|
sub _pixmap_extend { |
677
|
|
|
|
|
|
|
my ($self, $target_x) = @_; |
678
|
|
|
|
|
|
|
if (DEBUG >= 2) { |
679
|
|
|
|
|
|
|
my $last_index = $self->{'drawn'}->[-2]; |
680
|
|
|
|
|
|
|
my $pixmap_size = $self->{'pixmap_size'}; |
681
|
|
|
|
|
|
|
print "_pixmap_extend target_x=$target_x", |
682
|
|
|
|
|
|
|
" got last index=",(defined $last_index?$last_index:'undef'), |
683
|
|
|
|
|
|
|
",x=$self->{'pixmap_end_x'}", |
684
|
|
|
|
|
|
|
", pixmap_size=",(defined $pixmap_size ? $pixmap_size : '[undef]'), |
685
|
|
|
|
|
|
|
"\n"; |
686
|
|
|
|
|
|
|
} |
687
|
|
|
|
|
|
|
if (DEBUG) { |
688
|
|
|
|
|
|
|
if ($target_x > $self->{'pixmap_size'}) { |
689
|
|
|
|
|
|
|
die "oops, target_x=$target_x bigger than pixmap ", |
690
|
|
|
|
|
|
|
$self->{'pixmap_size'}; |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
} |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
my $x = $self->{'pixmap_end_x'}; |
695
|
|
|
|
|
|
|
if ($x >= $target_x) { return; } # if target already covered |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
my $pixmap = _pixmap($self); |
698
|
|
|
|
|
|
|
my ($pixmap_width, $pixmap_height) = $pixmap->get_size; |
699
|
|
|
|
|
|
|
my $pixmap_size = $self->{'pixmap_size'}; |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
my $model = $self->{'model'}; |
702
|
|
|
|
|
|
|
if (! $model) { |
703
|
|
|
|
|
|
|
### no model set |
704
|
|
|
|
|
|
|
EMPTY: |
705
|
|
|
|
|
|
|
$self->{'pixmap_end_x'} = $pixmap_size; |
706
|
|
|
|
|
|
|
return; |
707
|
|
|
|
|
|
|
} |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
# "pack_start"s first then "pack_end"s |
710
|
|
|
|
|
|
|
my @cellinfo_list = ($self->_cellinfo_starts, |
711
|
|
|
|
|
|
|
reverse $self->_cellinfo_ends); |
712
|
|
|
|
|
|
|
if (! @cellinfo_list) { |
713
|
|
|
|
|
|
|
### no cell renderers to draw with |
714
|
|
|
|
|
|
|
goto EMPTY; |
715
|
|
|
|
|
|
|
} |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
my $all_zeros = _make_all_zeros_proc(); |
718
|
|
|
|
|
|
|
my $ltor = ($self->get_direction eq 'ltr'); |
719
|
|
|
|
|
|
|
my $row_widths = $self->{'row_widths'}; |
720
|
|
|
|
|
|
|
my $drawn = $self->{'drawn_array'}; |
721
|
|
|
|
|
|
|
my $vertical = $self->{'vertical'}; |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
my $index = $drawn->[-2]; |
724
|
|
|
|
|
|
|
if (defined $index) { |
725
|
|
|
|
|
|
|
$index++; |
726
|
|
|
|
|
|
|
} else { |
727
|
|
|
|
|
|
|
$index = $self->{'want_index'}; |
728
|
|
|
|
|
|
|
} |
729
|
|
|
|
|
|
|
my $iter = $model->iter_nth_child (undef, $index); |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
for (;;) { |
732
|
|
|
|
|
|
|
if (! $iter) { |
733
|
|
|
|
|
|
|
# initial $index was past the end, or stepped iter_next() past the |
734
|
|
|
|
|
|
|
# end, either way wrap around |
735
|
|
|
|
|
|
|
$index = 0; |
736
|
|
|
|
|
|
|
$iter = $model->get_iter_first; |
737
|
|
|
|
|
|
|
if (! $iter) { |
738
|
|
|
|
|
|
|
### model has no rows |
739
|
|
|
|
|
|
|
$x = $pixmap_size; |
740
|
|
|
|
|
|
|
last; |
741
|
|
|
|
|
|
|
} |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
push @$drawn, $index, $x; |
745
|
|
|
|
|
|
|
$self->_set_cell_data ($iter); |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
my $row_size = 0; |
748
|
|
|
|
|
|
|
foreach my $cellinfo (@cellinfo_list) { |
749
|
|
|
|
|
|
|
my $cell = $cellinfo->{'cell'}; |
750
|
|
|
|
|
|
|
if (! $cell->get('visible')) { next; } |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
my (undef, undef, $width, $height) = $cell->get_size ($self, undef); |
753
|
|
|
|
|
|
|
my $rect; |
754
|
|
|
|
|
|
|
if ($vertical) { |
755
|
|
|
|
|
|
|
$rect = Gtk2::Gdk::Rectangle->new |
756
|
|
|
|
|
|
|
(0, $ltor ? $x : $pixmap_height - 1 - $x - $height, |
757
|
|
|
|
|
|
|
$pixmap_width, $height); |
758
|
|
|
|
|
|
|
$x += $height; |
759
|
|
|
|
|
|
|
$row_size += $height; |
760
|
|
|
|
|
|
|
} else { |
761
|
|
|
|
|
|
|
$rect = Gtk2::Gdk::Rectangle->new |
762
|
|
|
|
|
|
|
($ltor ? $x : $pixmap_width - 1 - $x - $width, 0, |
763
|
|
|
|
|
|
|
$width, $pixmap_height); |
764
|
|
|
|
|
|
|
$x += $width; |
765
|
|
|
|
|
|
|
$row_size += $width; |
766
|
|
|
|
|
|
|
} |
767
|
|
|
|
|
|
|
$cell->render ($pixmap, $self, $rect, $rect, $rect, []); |
768
|
|
|
|
|
|
|
} |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
$row_widths->{$index} = $row_size; |
771
|
|
|
|
|
|
|
if (DEBUG >= 2) { print " draw $index at x=",$x-$row_size, |
772
|
|
|
|
|
|
|
" width $row_size\n"; } |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
if ($all_zeros->($index, $row_size)) { |
775
|
|
|
|
|
|
|
### all cell widths on all rows are zero |
776
|
|
|
|
|
|
|
$self->{'want_x'} = 0; |
777
|
|
|
|
|
|
|
$x = $pixmap_size; |
778
|
|
|
|
|
|
|
last; |
779
|
|
|
|
|
|
|
} |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
$index++; |
782
|
|
|
|
|
|
|
if ($x >= $target_x) { last; } # stop when target covered |
783
|
|
|
|
|
|
|
$iter = $model->iter_next ($iter); |
784
|
|
|
|
|
|
|
} |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
$self->{'pixmap_end_x'} = min ($x, $pixmap_size); |
787
|
|
|
|
|
|
|
### extended to pixmap_end_x: $self->{'pixmap_end_x'} |
788
|
|
|
|
|
|
|
} |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
# _pixmap() returns 'pixmap', creating it if it doesn't already exist. |
791
|
|
|
|
|
|
|
# The height is the same as the window height. |
792
|
|
|
|
|
|
|
# The width is 1.5 * the window width, or half the screen width, whichever |
793
|
|
|
|
|
|
|
# is bigger. |
794
|
|
|
|
|
|
|
# |
795
|
|
|
|
|
|
|
# The pixmap is designed to avoid drawing the same row repeatedly as it |
796
|
|
|
|
|
|
|
# scrolls across. The wider the pixmap the less often a full redraw will be |
797
|
|
|
|
|
|
|
# needed. The width used is therefore a compromise between the memory taken |
798
|
|
|
|
|
|
|
# by a wide pixmap, versus redraws caused by a narrow pixmap. |
799
|
|
|
|
|
|
|
# |
800
|
|
|
|
|
|
|
# Twice the window width gives a reasonable amount of hidden pixmap |
801
|
|
|
|
|
|
|
# buffering off the window ends. However if the window is unusually narrow |
802
|
|
|
|
|
|
|
# it could be much less than a typical row, so impose a minimum of half the |
803
|
|
|
|
|
|
|
# screen width. |
804
|
|
|
|
|
|
|
# |
805
|
|
|
|
|
|
|
# (Maybe a maximum of say twice the screen width could be imposed too, so |
806
|
|
|
|
|
|
|
# that a hugely wide window doesn't result in a massive pixmap. But for a |
807
|
|
|
|
|
|
|
# pixmap smaller than the window we'd have to notice what portion of the |
808
|
|
|
|
|
|
|
# window is on-screen. Probably that's much more trouble than it's worth. |
809
|
|
|
|
|
|
|
# If you ask for a stupidly wide window then expect to have your pixmap |
810
|
|
|
|
|
|
|
# memory used up. :-) |
811
|
|
|
|
|
|
|
# |
812
|
|
|
|
|
|
|
sub _pixmap { |
813
|
|
|
|
|
|
|
my ($self) = @_; |
814
|
|
|
|
|
|
|
return ($self->{'pixmap'} ||= do { |
815
|
|
|
|
|
|
|
### _pixmap() create |
816
|
|
|
|
|
|
|
my ($pixmap_width, $pixmap_height) |
817
|
|
|
|
|
|
|
= _pixmap_desired_size ($self, $self->allocation); |
818
|
|
|
|
|
|
|
### create size: "${pixmap_width}x${pixmap_height}" |
819
|
|
|
|
|
|
|
Gtk2::Gdk::Pixmap->new ($self->window, $pixmap_width, $pixmap_height, -1); |
820
|
|
|
|
|
|
|
}); |
821
|
|
|
|
|
|
|
} |
822
|
|
|
|
|
|
|
use constant _PIXMAP_ALLOCATION_FACTOR => 1.5; |
823
|
|
|
|
|
|
|
use constant _SCREEN_SIZE_FACTOR => 0.5; |
824
|
|
|
|
|
|
|
sub _pixmap_desired_size { |
825
|
|
|
|
|
|
|
my ($self, $alloc) = @_; |
826
|
|
|
|
|
|
|
### _pixmap_desired_size() |
827
|
|
|
|
|
|
|
my @pixmap_dims = ($alloc->width, $alloc->height); |
828
|
|
|
|
|
|
|
my $i = $self->{'vertical'}; # width for horiz, height for vert |
829
|
|
|
|
|
|
|
my $screen = $self->get_screen; # gtk 2.4 for celllayout, so have 2.2 screen |
830
|
|
|
|
|
|
|
my @screen_dims = ($screen->get_width, $screen->get_height); |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
### max: "alloc*"._PIXMAP_ALLOCATION_FACTOR." = ".($pixmap_dims[$i] * _PIXMAP_ALLOCATION_FACTOR) |
833
|
|
|
|
|
|
|
### max: "screen*"._SCREEN_SIZE_FACTOR." = ".($screen_dims[$i] * _SCREEN_SIZE_FACTOR) |
834
|
|
|
|
|
|
|
$pixmap_dims[$i] = $self->{'pixmap_size'} |
835
|
|
|
|
|
|
|
= int (max ($pixmap_dims[$i] * _PIXMAP_ALLOCATION_FACTOR, |
836
|
|
|
|
|
|
|
$screen_dims[$i] * _SCREEN_SIZE_FACTOR)); |
837
|
|
|
|
|
|
|
### desire: "$pixmap_dims[0]x$pixmap_dims[1]" |
838
|
|
|
|
|
|
|
return @pixmap_dims; |
839
|
|
|
|
|
|
|
} |
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
sub _pixmap_queue_draw { |
842
|
|
|
|
|
|
|
my ($self) = @_; |
843
|
|
|
|
|
|
|
### _pixmap_queue_draw() |
844
|
|
|
|
|
|
|
# zap 'drawn_array' to save work in _apply_remap |
845
|
|
|
|
|
|
|
$self->{'pixmap_redraw'} = 1; |
846
|
|
|
|
|
|
|
@{$self->{'drawn_array'}} = (); |
847
|
|
|
|
|
|
|
$self->queue_draw; |
848
|
|
|
|
|
|
|
} |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
#----------------------------------------------------------------------------- |
851
|
|
|
|
|
|
|
# row index widths and normalization |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
# Normalize $x,$index so that $x<=0 and $x+$row_width >= 0. |
854
|
|
|
|
|
|
|
# The return is two new values ($x,$index). |
855
|
|
|
|
|
|
|
# If $x==0 then $x,$index are returned unchanged. |
856
|
|
|
|
|
|
|
# If there's no model, or the model is empty, the return is empty (). |
857
|
|
|
|
|
|
|
# If all rows are zero width the return is $x==undef and $index unchanged. |
858
|
|
|
|
|
|
|
# |
859
|
|
|
|
|
|
|
sub _normalize { |
860
|
|
|
|
|
|
|
my ($self, $x, $index) = @_; |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
my $model = $self->{'model'} || return; |
863
|
|
|
|
|
|
|
my $all_zeros = _make_all_zeros_proc(); |
864
|
|
|
|
|
|
|
my $len = $model->iter_n_children(undef) || return; # if model empty |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
if ($x < 0) { |
867
|
|
|
|
|
|
|
# Here we're looking to see if the want_index row is entirely |
868
|
|
|
|
|
|
|
# off-screen, ie. if want_x + row_width (of that row) would still be |
869
|
|
|
|
|
|
|
# want_x <= 0. |
870
|
|
|
|
|
|
|
# |
871
|
|
|
|
|
|
|
# If _row_width() gives us a $iter, because it used it to get a row |
872
|
|
|
|
|
|
|
# width, then we keep it going for further rows. If _row_width() |
873
|
|
|
|
|
|
|
# operates out of its cache then there's no iter. |
874
|
|
|
|
|
|
|
# |
875
|
|
|
|
|
|
|
### forward from: "$index,$x" |
876
|
|
|
|
|
|
|
my $iter; |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
for (;;) { |
879
|
|
|
|
|
|
|
my $row_width = _row_width ($self, $index, $iter); |
880
|
|
|
|
|
|
|
if ($x + $row_width > 0) { |
881
|
|
|
|
|
|
|
last; |
882
|
|
|
|
|
|
|
} |
883
|
|
|
|
|
|
|
if ($all_zeros->($index, $row_width)) { |
884
|
|
|
|
|
|
|
### all cell widths on all rows are zero |
885
|
|
|
|
|
|
|
return (undef, $_[2]); # with original $index |
886
|
|
|
|
|
|
|
} |
887
|
|
|
|
|
|
|
$x += $row_width; |
888
|
|
|
|
|
|
|
$index++; |
889
|
|
|
|
|
|
|
if ($index >= $len) { |
890
|
|
|
|
|
|
|
$index = 0; |
891
|
|
|
|
|
|
|
$iter = undef; |
892
|
|
|
|
|
|
|
} else { |
893
|
|
|
|
|
|
|
if ($iter) { |
894
|
|
|
|
|
|
|
$iter = $model->iter_next ($iter); |
895
|
|
|
|
|
|
|
} |
896
|
|
|
|
|
|
|
} |
897
|
|
|
|
|
|
|
} |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
} else { |
900
|
|
|
|
|
|
|
# Here we're trying to bring $x back to <= 0, usually because a backward |
901
|
|
|
|
|
|
|
# scroll has pushed our want_x position to the right and we have to see |
902
|
|
|
|
|
|
|
# what the preceding row is and want position to draw it. |
903
|
|
|
|
|
|
|
# |
904
|
|
|
|
|
|
|
# Because there's no "iter_prev" there's no use of iters here, it ends |
905
|
|
|
|
|
|
|
# up a new iter_nth_child in _row_width() for every row not already |
906
|
|
|
|
|
|
|
# cached. For a user scroll back just a short distance the previous row |
907
|
|
|
|
|
|
|
# is probably already cached (and even probably in the pixmap). |
908
|
|
|
|
|
|
|
# |
909
|
|
|
|
|
|
|
### backward from: "$x,$index" |
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
while ($x > 0) { |
912
|
|
|
|
|
|
|
$index--; |
913
|
|
|
|
|
|
|
if ($index < 0) { |
914
|
|
|
|
|
|
|
$index = max (0, $len-1); |
915
|
|
|
|
|
|
|
} |
916
|
|
|
|
|
|
|
my $row_width = _row_width ($self, $index); |
917
|
|
|
|
|
|
|
if ($all_zeros->($index, $row_width)) { |
918
|
|
|
|
|
|
|
### all cell widths on all rows are zero |
919
|
|
|
|
|
|
|
return (undef, $_[2]); # with original $index |
920
|
|
|
|
|
|
|
} |
921
|
|
|
|
|
|
|
$x -= $row_width; |
922
|
|
|
|
|
|
|
} |
923
|
|
|
|
|
|
|
} |
924
|
|
|
|
|
|
|
#### now at "$index,$x" |
925
|
|
|
|
|
|
|
return ($x, $index); |
926
|
|
|
|
|
|
|
} |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
# Return the width in pixels of row $index, or height when vertical. |
929
|
|
|
|
|
|
|
# $iter is an iterator for $index, or undef to make one here if necessary. |
930
|
|
|
|
|
|
|
# If an iterator is made then it's stored back to $_[2], as call-by-reference. |
931
|
|
|
|
|
|
|
# |
932
|
|
|
|
|
|
|
sub _row_width { |
933
|
|
|
|
|
|
|
my ($self, $index, $iter) = @_; |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
my $row_widths = $self->{'row_widths'}; |
936
|
|
|
|
|
|
|
my $row_width = $row_widths->{$index}; |
937
|
|
|
|
|
|
|
if (defined $row_width) { return $row_width; } |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
if (DEBUG) { print " _row_width on $index, iter=", |
940
|
|
|
|
|
|
|
(defined $iter ? $iter : 'undef'), "\n"; } |
941
|
|
|
|
|
|
|
if (! defined $iter) { |
942
|
|
|
|
|
|
|
my $model = $self->{'model'}; |
943
|
|
|
|
|
|
|
$iter = $_[2] = $model && $model->iter_nth_child (undef, $index); |
944
|
|
|
|
|
|
|
if (! defined $iter) { |
945
|
|
|
|
|
|
|
if (DEBUG) { print " _row_width index $index out of range\n"; } |
946
|
|
|
|
|
|
|
return 0; |
947
|
|
|
|
|
|
|
} |
948
|
|
|
|
|
|
|
} |
949
|
|
|
|
|
|
|
$self->_set_cell_data ($iter); |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
my $sizefield = 2 + $self->{'vertical'}; # width horiz, height vert |
952
|
|
|
|
|
|
|
$row_width = 0; |
953
|
|
|
|
|
|
|
foreach my $cellinfo (@{$self->{'cellinfo_list'}}) { |
954
|
|
|
|
|
|
|
my $cell = $cellinfo->{'cell'}; |
955
|
|
|
|
|
|
|
if (! $cell->get('visible')) { next; } |
956
|
|
|
|
|
|
|
$row_width += ($cell->get_size ($self,undef)) [$sizefield]; |
957
|
|
|
|
|
|
|
} |
958
|
|
|
|
|
|
|
### _row_width() calc: "$index is $row_width" |
959
|
|
|
|
|
|
|
return ($row_widths->{$index} = $row_width); |
960
|
|
|
|
|
|
|
} |
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
963
|
|
|
|
|
|
|
# programmatic scrolling |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
sub scroll_to_start { |
966
|
|
|
|
|
|
|
my ($self) = @_; |
967
|
|
|
|
|
|
|
_scroll_to_pos ($self, 0, 0); |
968
|
|
|
|
|
|
|
} |
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
sub scroll_pixels { |
971
|
|
|
|
|
|
|
my ($self, $pixels) = @_; |
972
|
|
|
|
|
|
|
_scroll_to_pos ($self, $self->{'want_x'} - $pixels, $self->{'want_index'}); |
973
|
|
|
|
|
|
|
} |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
sub _scroll_to_pos { |
976
|
|
|
|
|
|
|
my ($self, $x, $index) = @_; |
977
|
|
|
|
|
|
|
#### _scroll_to_pos(): "x=$x index=$index" |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
$self->{'want_index'} = $index; |
980
|
|
|
|
|
|
|
$self->{'want_x'} = $x; |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
# If drawable(), ie. GTK_WIDGET_DRAWABLE(), is true, meaning widget |
983
|
|
|
|
|
|
|
# show()ed and window mapped, then draw after a server sync. If unmapped |
984
|
|
|
|
|
|
|
# then programmatic scrolls just move the position around and the eventual |
985
|
|
|
|
|
|
|
# map will get an expose to draw. |
986
|
|
|
|
|
|
|
# |
987
|
|
|
|
|
|
|
if ($self->drawable) { |
988
|
|
|
|
|
|
|
$self->{'sync_call'} ||= do { |
989
|
|
|
|
|
|
|
my $weak_self = $self; |
990
|
|
|
|
|
|
|
Scalar::Util::weaken ($weak_self); |
991
|
|
|
|
|
|
|
Gtk2::Ex::SyncCall->sync ($self, \&_sync_call_handler, \$weak_self); |
992
|
|
|
|
|
|
|
}; |
993
|
|
|
|
|
|
|
} |
994
|
|
|
|
|
|
|
} |
995
|
|
|
|
|
|
|
sub _sync_call_handler { |
996
|
|
|
|
|
|
|
my ($ref_weak_self) = @_; |
997
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return; |
998
|
|
|
|
|
|
|
#### TickerView _sync_call_handler() |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
$self->{'sync_call'} = undef; |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
# Recheck drawable(), GTK_WIDGET_DRAWABLE(), in case unmapped in between |
1003
|
|
|
|
|
|
|
# the last scroll and the server sync reply. If still mapped then do an |
1004
|
|
|
|
|
|
|
# immediate draw via queue_draw and forced update. |
1005
|
|
|
|
|
|
|
# |
1006
|
|
|
|
|
|
|
# process_updates() normally runs under an idle at GDK_PRIORITY_REDRAW, |
1007
|
|
|
|
|
|
|
# but don't wait to get down there. Currently at GDK_PRIORITY_EVENTS from |
1008
|
|
|
|
|
|
|
# the sync and the sync means now is a good time for a forced draw. |
1009
|
|
|
|
|
|
|
# |
1010
|
|
|
|
|
|
|
if ($self->drawable) { |
1011
|
|
|
|
|
|
|
$self->queue_draw; |
1012
|
|
|
|
|
|
|
$self->window->process_updates (1); |
1013
|
|
|
|
|
|
|
} |
1014
|
|
|
|
|
|
|
} |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
1018
|
|
|
|
|
|
|
# drawing style changes |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
# 'direction_changed' class closure |
1021
|
|
|
|
|
|
|
sub _do_direction_changed { |
1022
|
|
|
|
|
|
|
my ($self, $prev_dir) = @_; |
1023
|
|
|
|
|
|
|
_pixmap_queue_draw ($self); |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
# As of Gtk 2.18 the GtkWidget code in gtk_widget_real_direction_changed() |
1026
|
|
|
|
|
|
|
# (previously called gtk_widget_direction_changed()) does a queue_resize(), |
1027
|
|
|
|
|
|
|
# which is neither needed or wanted here. But a direction change should |
1028
|
|
|
|
|
|
|
# be infrequent and better make sure anything GtkWidget does in the future |
1029
|
|
|
|
|
|
|
# gets run. |
1030
|
|
|
|
|
|
|
$self->signal_chain_from_overridden ($prev_dir); |
1031
|
|
|
|
|
|
|
} |
1032
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
# 'notify' class closure |
1034
|
|
|
|
|
|
|
# SET_PROPERTY() is called only for own class properties, this default |
1035
|
|
|
|
|
|
|
# handler sees changes to those defined by the GtkWidget superclass (and all |
1036
|
|
|
|
|
|
|
# other sub and super classes) |
1037
|
|
|
|
|
|
|
sub _do_notify { |
1038
|
|
|
|
|
|
|
my ($self, $pspec) = @_; |
1039
|
|
|
|
|
|
|
my $pname = $pspec->get_name; |
1040
|
|
|
|
|
|
|
### TickerView _do_notify(): $pname |
1041
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
if ($pname eq 'sensitive') { |
1043
|
|
|
|
|
|
|
# gtk_widget_set_sensitive does $self->queue_draw, so just need to |
1044
|
|
|
|
|
|
|
# invalidate pixmap contents here for a redraw |
1045
|
|
|
|
|
|
|
### redraw |
1046
|
|
|
|
|
|
|
_pixmap_queue_draw ($self); |
1047
|
|
|
|
|
|
|
} |
1048
|
|
|
|
|
|
|
$self->signal_chain_from_overridden ($pspec); |
1049
|
|
|
|
|
|
|
} |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
# 'state_changed' class closure ($self, $state) |
1052
|
|
|
|
|
|
|
# 'style_set' class closure ($self, $prev_style) |
1053
|
|
|
|
|
|
|
sub _do_state_or_style_changed { |
1054
|
|
|
|
|
|
|
my ($self, $arg) = @_; |
1055
|
|
|
|
|
|
|
if (DEBUG) { print "TickerView state-changed or style-set '", |
1056
|
|
|
|
|
|
|
(defined $arg ? $arg : 'undef'),"'\n"; } |
1057
|
|
|
|
|
|
|
_pixmap_queue_draw ($self); |
1058
|
|
|
|
|
|
|
$self->signal_chain_from_overridden ($arg); |
1059
|
|
|
|
|
|
|
} |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
1063
|
|
|
|
|
|
|
# scroll timer |
1064
|
|
|
|
|
|
|
# |
1065
|
|
|
|
|
|
|
# _update_timer() starts or stops the timer according to the numerous |
1066
|
|
|
|
|
|
|
# conditions set out in that func. In general the idea is not to run the |
1067
|
|
|
|
|
|
|
# timer when there's nothing to see and/or move: eg. the window is not |
1068
|
|
|
|
|
|
|
# visible, or there's nothing in the model+renderers. Care must be taken to |
1069
|
|
|
|
|
|
|
# call _update_timer() when any of the conditions may have changed. |
1070
|
|
|
|
|
|
|
# |
1071
|
|
|
|
|
|
|
# The timer action itself is pretty simple, it just calls the public |
1072
|
|
|
|
|
|
|
# $self->scroll_pixels() to make the move, by an amount based on elapsed |
1073
|
|
|
|
|
|
|
# real time, per "OTHER NOTES" in the pod below. The hairy stuff in |
1074
|
|
|
|
|
|
|
# scroll_pixels() collapsing multiple motions and drawing works just as well |
1075
|
|
|
|
|
|
|
# from the timer as it does from application uses of that func. In fact the |
1076
|
|
|
|
|
|
|
# collapsing exists mainly to help if the timer runs a touch too fast for |
1077
|
|
|
|
|
|
|
# the server's drawing. |
1078
|
|
|
|
|
|
|
# |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
# _TIMER_PRIORITY is below the drawing priority GDK_PRIORITY_EVENTS in |
1081
|
|
|
|
|
|
|
# _sync_call_handler(), so drawing of the current position goes out before |
1082
|
|
|
|
|
|
|
# making a scroll to a new position. |
1083
|
|
|
|
|
|
|
# |
1084
|
|
|
|
|
|
|
# Try _TIMER_PRIORITY below GDK_PRIORITY_REDRAW too, to hopefully cooperate |
1085
|
|
|
|
|
|
|
# with redrawing of other widgets, letting their drawing go out before |
1086
|
|
|
|
|
|
|
# scrolling the ticker. |
1087
|
|
|
|
|
|
|
# |
1088
|
|
|
|
|
|
|
use constant _TIMER_PRIORITY => (GDK_PRIORITY_REDRAW + 10); |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
# _gettime() returns a floating point count of seconds since some fixed but |
1091
|
|
|
|
|
|
|
# unspecified origin time. |
1092
|
|
|
|
|
|
|
# |
1093
|
|
|
|
|
|
|
# clock_gettime(CLOCK_REALTIME) is preferred. clock_gettime() always |
1094
|
|
|
|
|
|
|
# exists, but it croaks if there's no such C library func. In that case |
1095
|
|
|
|
|
|
|
# fall back on the hires time(), which is whatever best thing Time::HiRes |
1096
|
|
|
|
|
|
|
# can do, probably gettimeofday() normally. |
1097
|
|
|
|
|
|
|
# |
1098
|
|
|
|
|
|
|
# Maybe it'd be worth checking clock_getres() to see it's a decent |
1099
|
|
|
|
|
|
|
# resolution. It's conceivable some old implementations might do |
1100
|
|
|
|
|
|
|
# CLOCK_REALTIME just from the CLK_TCK times() counter, giving only 10 |
1101
|
|
|
|
|
|
|
# millisecond resolution. That's enough for a modest 10 or 20 frames/sec, |
1102
|
|
|
|
|
|
|
# but if attempting say 100 frames on a fast computer for ultra smoothness |
1103
|
|
|
|
|
|
|
# then higher resolution would be needed. |
1104
|
|
|
|
|
|
|
# |
1105
|
|
|
|
|
|
|
sub _gettime { |
1106
|
|
|
|
|
|
|
return Time::HiRes::clock_gettime (Time::HiRes::CLOCK_REALTIME()); |
1107
|
|
|
|
|
|
|
} |
1108
|
|
|
|
|
|
|
unless (eval { _gettime(); 1 }) { |
1109
|
|
|
|
|
|
|
### TickerView fallback to Time HiRes time() due to clock_gettime() error: $@ |
1110
|
|
|
|
|
|
|
no warnings; |
1111
|
|
|
|
|
|
|
*_gettime = \&Time::HiRes::time; |
1112
|
|
|
|
|
|
|
} |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
# start or stop the scroll timer according to the various settings |
1115
|
|
|
|
|
|
|
sub _update_timer { |
1116
|
|
|
|
|
|
|
my ($self) = @_; |
1117
|
|
|
|
|
|
|
|
1118
|
|
|
|
|
|
|
my $want_timer = $self->{'run'} |
1119
|
|
|
|
|
|
|
&& ! $self->{'paused_count'} |
1120
|
|
|
|
|
|
|
&& $self->mapped |
1121
|
|
|
|
|
|
|
&& $self->{'visibility_state'} ne 'fully-obscured' |
1122
|
|
|
|
|
|
|
&& $self->{'cellinfo_list'} |
1123
|
|
|
|
|
|
|
&& @{$self->{'cellinfo_list'}} # renderer list not empty |
1124
|
|
|
|
|
|
|
&& $self->{'frame_rate'} > 0 |
1125
|
|
|
|
|
|
|
&& ! defined $self->{'drag_xy'} # not in a drag |
1126
|
|
|
|
|
|
|
&& $self->{'model'} |
1127
|
|
|
|
|
|
|
&& ! $self->{'model_empty'}; |
1128
|
|
|
|
|
|
|
|
1129
|
|
|
|
|
|
|
if (DEBUG) { |
1130
|
|
|
|
|
|
|
print " _update_timer run=", $self->{'run'}, |
1131
|
|
|
|
|
|
|
" paused=", ($self->{'paused_count'}||'no'), |
1132
|
|
|
|
|
|
|
" mapped=", $self->mapped ? 1 : 0, |
1133
|
|
|
|
|
|
|
" visibility=", $self->{'visibility_state'}, |
1134
|
|
|
|
|
|
|
" model=", ($self->{'model'} ? 'yes' : 'none'), |
1135
|
|
|
|
|
|
|
" model_empty=", $self->{'model_empty'} || '0', |
1136
|
|
|
|
|
|
|
" (iter_first=", (! defined $self->{'model'} ? 'n/a)' |
1137
|
|
|
|
|
|
|
: $self->{'model'}->get_iter_first ? 'yes)' : 'no)'), |
1138
|
|
|
|
|
|
|
" --> want ", ($want_timer ? 'yes' : 'no'), "\n"; |
1139
|
|
|
|
|
|
|
} |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
$self->{'timer'} = $want_timer && |
1142
|
|
|
|
|
|
|
($self->{'timer'} # existing timer if already running |
1143
|
|
|
|
|
|
|
|| do { # otherwise a new one |
1144
|
|
|
|
|
|
|
my $period = POSIX::ceil (1000.0 / $self->{'frame_rate'}); |
1145
|
|
|
|
|
|
|
if (DEBUG) { print "TickerView start timer, $period ms\n"; } |
1146
|
|
|
|
|
|
|
|
1147
|
|
|
|
|
|
|
my $weak_self = $self; |
1148
|
|
|
|
|
|
|
Scalar::Util::weaken ($weak_self); |
1149
|
|
|
|
|
|
|
$self->{'prev_time'} = _gettime(); |
1150
|
|
|
|
|
|
|
require Glib::Ex::SourceIds; |
1151
|
|
|
|
|
|
|
Glib::Ex::SourceIds->new |
1152
|
|
|
|
|
|
|
(Glib::Timeout->add ($period, \&_do_timer, \$weak_self, |
1153
|
|
|
|
|
|
|
_TIMER_PRIORITY)); |
1154
|
|
|
|
|
|
|
}); |
1155
|
|
|
|
|
|
|
} |
1156
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
sub _do_timer { |
1158
|
|
|
|
|
|
|
my ($ref_weak_self) = @_; |
1159
|
|
|
|
|
|
|
# shouldn't see an undef in $$ref_weak_self because the timer should be |
1160
|
|
|
|
|
|
|
# stopped already by _do_unrealize in the course of widget destruction, |
1161
|
|
|
|
|
|
|
# but if for some reason that hasn't happened then stop it now |
1162
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return 0; # Glib::SOURCE_REMOVE |
1163
|
|
|
|
|
|
|
|
1164
|
|
|
|
|
|
|
my $t = _gettime(); |
1165
|
|
|
|
|
|
|
my $delta = $t - $self->{'prev_time'}; |
1166
|
|
|
|
|
|
|
$self->{'prev_time'} = $t; |
1167
|
|
|
|
|
|
|
|
1168
|
|
|
|
|
|
|
# Watch out for the clock going backwards, don't want to scroll back. |
1169
|
|
|
|
|
|
|
# Watch out for jumping wildly forwards too due to the process blocked for |
1170
|
|
|
|
|
|
|
# a while, don't want to churn through some massive pixel count forwards. |
1171
|
|
|
|
|
|
|
$delta = min (5, max (0, $delta)); |
1172
|
|
|
|
|
|
|
|
1173
|
|
|
|
|
|
|
my $step = $self->{'speed'} * $delta; |
1174
|
|
|
|
|
|
|
$self->scroll_pixels ($step); |
1175
|
|
|
|
|
|
|
if (DEBUG >= 2) { print "_do_timer scroll $delta seconds, $step pixels,", |
1176
|
|
|
|
|
|
|
" to $self->{'want_x'}\n"; } |
1177
|
|
|
|
|
|
|
return 1; # Glib::SOURCE_CONTINUE |
1178
|
|
|
|
|
|
|
} |
1179
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
# 'map' class closure ($self) |
1181
|
|
|
|
|
|
|
# 'unmap' class closure ($self) |
1182
|
|
|
|
|
|
|
# |
1183
|
|
|
|
|
|
|
# This is asking the widget to map or unmap itself, not map-event or |
1184
|
|
|
|
|
|
|
# unmap-event back from the server. |
1185
|
|
|
|
|
|
|
# |
1186
|
|
|
|
|
|
|
sub _do_map_or_unmap { |
1187
|
|
|
|
|
|
|
my ($self) = @_; |
1188
|
|
|
|
|
|
|
### TickerView _do_map_or_unmap() |
1189
|
|
|
|
|
|
|
|
1190
|
|
|
|
|
|
|
# chain before _update_timer(), so the GtkWidget code sets or unsets the |
1191
|
|
|
|
|
|
|
# mapped flag which _update_timer() will look at |
1192
|
|
|
|
|
|
|
$self->signal_chain_from_overridden; |
1193
|
|
|
|
|
|
|
_update_timer ($self); |
1194
|
|
|
|
|
|
|
} |
1195
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
# 'unrealize' class closure |
1197
|
|
|
|
|
|
|
# (asking the widget to unrealize itself) |
1198
|
|
|
|
|
|
|
# |
1199
|
|
|
|
|
|
|
# When a ticker is removed from a container only unrealize is called, not |
1200
|
|
|
|
|
|
|
# unmap then unrealize, hence an _update_timer() check here as well as |
1201
|
|
|
|
|
|
|
# _do_map_or_unmap() above. |
1202
|
|
|
|
|
|
|
# |
1203
|
|
|
|
|
|
|
sub _do_unrealize { |
1204
|
|
|
|
|
|
|
my ($self) = @_; |
1205
|
|
|
|
|
|
|
### TickerView _do_unrealize() |
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
# chain before _update_timer(), so the GtkWidget code clears the mapped flag |
1208
|
|
|
|
|
|
|
$self->signal_chain_from_overridden; |
1209
|
|
|
|
|
|
|
|
1210
|
|
|
|
|
|
|
@{$self->{'drawn_array'}} = (); # full redraw if realized again later |
1211
|
|
|
|
|
|
|
$self->{'pixmap'} = undef; # possible different depth if realized again later |
1212
|
|
|
|
|
|
|
_update_timer ($self); |
1213
|
|
|
|
|
|
|
} |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
# 'visibility_notify_event' class closure |
1216
|
|
|
|
|
|
|
sub _do_visibility_notify_event { |
1217
|
|
|
|
|
|
|
my ($self, $event) = @_; |
1218
|
|
|
|
|
|
|
### TickerView _do_visibility_notify_event(): $event->state |
1219
|
|
|
|
|
|
|
$self->{'visibility_state'} = $event->state; |
1220
|
|
|
|
|
|
|
_update_timer ($self); |
1221
|
|
|
|
|
|
|
return $self->signal_chain_from_overridden ($event); |
1222
|
|
|
|
|
|
|
} |
1223
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
1226
|
|
|
|
|
|
|
# dragging |
1227
|
|
|
|
|
|
|
# |
1228
|
|
|
|
|
|
|
# The basic operation here is pretty simple, it's just a matter of calling |
1229
|
|
|
|
|
|
|
# the public $self->scroll_pixels() with each mouse move amount as reported |
1230
|
|
|
|
|
|
|
# by motion-notify. The hairy stuff in scroll_pixels() to collapse moving |
1231
|
|
|
|
|
|
|
# and drawing works just as well for moves here as for application calls. |
1232
|
|
|
|
|
|
|
# |
1233
|
|
|
|
|
|
|
# If someone does a grab_pointer, either within the program or another |
1234
|
|
|
|
|
|
|
# client, then we'll no longer get motion notifies. Should timer based |
1235
|
|
|
|
|
|
|
# scrolling resume immediately, or only on button release? If the new grab |
1236
|
|
|
|
|
|
|
# is some unrelated action taking over then immediately might be best. But |
1237
|
|
|
|
|
|
|
# only on button release may be more consistent, in having the timer |
1238
|
|
|
|
|
|
|
# scrolling resume only on button release. The latter is done for now. |
1239
|
|
|
|
|
|
|
# |
1240
|
|
|
|
|
|
|
# If the window is moved during the drag, either repositioned by application |
1241
|
|
|
|
|
|
|
# code, or repositioned by the window manager etc, then it's possible to |
1242
|
|
|
|
|
|
|
# either |
1243
|
|
|
|
|
|
|
# |
1244
|
|
|
|
|
|
|
# 1. Let the displayed contents stay with the left edge of the window. |
1245
|
|
|
|
|
|
|
# 2. Let the displayed contents stay with the mouse, so it's like the |
1246
|
|
|
|
|
|
|
# window move reveals a different portion. |
1247
|
|
|
|
|
|
|
# |
1248
|
|
|
|
|
|
|
# Neither is too difficult, but 1 is adopted since in 2 there's a bit of |
1249
|
|
|
|
|
|
|
# flashing when the server copies the contents with the move and they then |
1250
|
|
|
|
|
|
|
# have to be redrawn. (Redrawn under size-allocate, since there's only |
1251
|
|
|
|
|
|
|
# configure-notify for a window move, no mouse motion-notify event.) |
1252
|
|
|
|
|
|
|
# |
1253
|
|
|
|
|
|
|
# Perhaps the double-buffering extension could help with the flashing, but |
1254
|
|
|
|
|
|
|
# it'd have to be applied to the parent window, and could only work when the |
1255
|
|
|
|
|
|
|
# move originates client-side, not say from the window manager. For now 1 |
1256
|
|
|
|
|
|
|
# is easier, and window moves during a drag should be fairly unusual anyway. |
1257
|
|
|
|
|
|
|
# |
1258
|
|
|
|
|
|
|
# To implement 1 the mouse position for dragging is maintained in root |
1259
|
|
|
|
|
|
|
# window coordinates. This means it's independent of the ticker window |
1260
|
|
|
|
|
|
|
# position. On that basis don't need to pay any attention to the window |
1261
|
|
|
|
|
|
|
# position, simply apply root window based mouse motion to scroll_pixels(). |
1262
|
|
|
|
|
|
|
# Both x and y are maintained so that you can actually change the |
1263
|
|
|
|
|
|
|
# "orientation" property in the middle of a drag and still get the right |
1264
|
|
|
|
|
|
|
# result! |
1265
|
|
|
|
|
|
|
# |
1266
|
|
|
|
|
|
|
|
1267
|
|
|
|
|
|
|
# if (0) { |
1268
|
|
|
|
|
|
|
# my $bindings = Gtk2::BindingSet->new ('Gtk2__Ex__TickerView'); |
1269
|
|
|
|
|
|
|
# $bindings->entry_add_signal |
1270
|
|
|
|
|
|
|
# (Gtk2::Gdk->keyval_from_name('Pointer_Button1'),[], |
1271
|
|
|
|
|
|
|
# 'start-drag'); |
1272
|
|
|
|
|
|
|
# $bindings->entry_add_signal |
1273
|
|
|
|
|
|
|
# (Gtk2::Gdk->keyval_from_name('Pointer_Button1'),['release-mask'], |
1274
|
|
|
|
|
|
|
# 'end-drag'); |
1275
|
|
|
|
|
|
|
# # priority level "gtk" treating this as widget level default, for |
1276
|
|
|
|
|
|
|
# # overriding by application or user RC |
1277
|
|
|
|
|
|
|
# $bindings->add_path ('class', 'Gtk2__Ex__TickerView', 'gtk'); |
1278
|
|
|
|
|
|
|
# } |
1279
|
|
|
|
|
|
|
|
1280
|
|
|
|
|
|
|
sub is_drag_active { |
1281
|
|
|
|
|
|
|
my ($self) = @_; |
1282
|
|
|
|
|
|
|
return (defined $self->{'drag_xy'}); |
1283
|
|
|
|
|
|
|
} |
1284
|
|
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
# 'button_press_event' class closure, getting Gtk2::Gdk::Event::Button |
1286
|
|
|
|
|
|
|
sub _do_button_press_event { |
1287
|
|
|
|
|
|
|
my ($self, $event) = @_; |
1288
|
|
|
|
|
|
|
#### TickerView button_press: $event->button |
1289
|
|
|
|
|
|
|
if ($event->button == 1) { |
1290
|
|
|
|
|
|
|
$self->{'drag_xy'} = [ $event->root_coords ]; |
1291
|
|
|
|
|
|
|
_update_timer ($self); # stop timer |
1292
|
|
|
|
|
|
|
} |
1293
|
|
|
|
|
|
|
return $self->signal_chain_from_overridden ($event); |
1294
|
|
|
|
|
|
|
} |
1295
|
|
|
|
|
|
|
|
1296
|
|
|
|
|
|
|
# 'motion_notify_event' class closure, getting Gtk2::Gdk::Event::Motion |
1297
|
|
|
|
|
|
|
# |
1298
|
|
|
|
|
|
|
# Use of is_hint() supports 'pointer-motion-hint-mask' perhaps set by the |
1299
|
|
|
|
|
|
|
# application or some add-on feature. Dragging only runs from a mouse |
1300
|
|
|
|
|
|
|
# button so for now it's enough to use get_pointer() rather than |
1301
|
|
|
|
|
|
|
# $display->get_state(). |
1302
|
|
|
|
|
|
|
# |
1303
|
|
|
|
|
|
|
sub _do_motion_notify_event { |
1304
|
|
|
|
|
|
|
my ($self, $event) = @_; |
1305
|
|
|
|
|
|
|
#### TickerView _do_motion_notify_event() |
1306
|
|
|
|
|
|
|
if (defined $self->{'drag_xy'}) { # ignore motion/drags of other buttons |
1307
|
|
|
|
|
|
|
_drag_scroll ($self, $event); |
1308
|
|
|
|
|
|
|
} |
1309
|
|
|
|
|
|
|
return $self->signal_chain_from_overridden ($event); |
1310
|
|
|
|
|
|
|
} |
1311
|
|
|
|
|
|
|
|
1312
|
|
|
|
|
|
|
# 'button_release_event' class closure, getting Gtk2::Gdk::Event::Button |
1313
|
|
|
|
|
|
|
# |
1314
|
|
|
|
|
|
|
sub _do_button_release_event { |
1315
|
|
|
|
|
|
|
my ($self, $event) = @_; |
1316
|
|
|
|
|
|
|
#### TickerView _do_button_release_event(): $event->button |
1317
|
|
|
|
|
|
|
|
1318
|
|
|
|
|
|
|
if (defined $self->{'drag_xy'} && $event->button == 1) { |
1319
|
|
|
|
|
|
|
_drag_scroll ($self, $event); # final dragged position from this event |
1320
|
|
|
|
|
|
|
delete $self->{'drag_xy'}; |
1321
|
|
|
|
|
|
|
_update_timer ($self); # restart timer |
1322
|
|
|
|
|
|
|
} |
1323
|
|
|
|
|
|
|
return $self->signal_chain_from_overridden ($event); |
1324
|
|
|
|
|
|
|
} |
1325
|
|
|
|
|
|
|
|
1326
|
|
|
|
|
|
|
# $event is either Gtk2::Gdk::Event::Motion or Gtk2::Gdk::Event::Button |
1327
|
|
|
|
|
|
|
sub _drag_scroll { |
1328
|
|
|
|
|
|
|
my ($self, $event) = @_; |
1329
|
|
|
|
|
|
|
if (DEBUG >= 2) { print " _drag_scroll ", |
1330
|
|
|
|
|
|
|
($event->can('is_hint') && $event->is_hint |
1331
|
|
|
|
|
|
|
? 'hint' : 'not-hint'), "\n"; } |
1332
|
|
|
|
|
|
|
|
1333
|
|
|
|
|
|
|
my @xy = ($event->can('is_hint') && $event->is_hint |
1334
|
|
|
|
|
|
|
? $self->get_root_window->get_pointer |
1335
|
|
|
|
|
|
|
: $event->root_coords); |
1336
|
|
|
|
|
|
|
|
1337
|
|
|
|
|
|
|
# step is simply how much the new position has moved from the old one |
1338
|
|
|
|
|
|
|
my $i = $self->{'vertical'}; |
1339
|
|
|
|
|
|
|
my $step = $self->{'drag_xy'}->[$i] - $xy[$i]; |
1340
|
|
|
|
|
|
|
@{$self->{'drag_xy'}} = @xy; |
1341
|
|
|
|
|
|
|
|
1342
|
|
|
|
|
|
|
if ($self->get_direction eq 'rtl') { $step = -$step; } |
1343
|
|
|
|
|
|
|
if (DEBUG >= 2) { print " step $step\n"; } |
1344
|
|
|
|
|
|
|
$self->scroll_pixels ($step); |
1345
|
|
|
|
|
|
|
} |
1346
|
|
|
|
|
|
|
|
1347
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
1348
|
|
|
|
|
|
|
# mouse wheel scroll |
1349
|
|
|
|
|
|
|
|
1350
|
|
|
|
|
|
|
my %direction_sign = (up => -1, |
1351
|
|
|
|
|
|
|
down => 1, |
1352
|
|
|
|
|
|
|
left => -1, |
1353
|
|
|
|
|
|
|
right => 1); |
1354
|
|
|
|
|
|
|
my %direction_is_vertical = (up => 1, |
1355
|
|
|
|
|
|
|
down => 1, |
1356
|
|
|
|
|
|
|
left => 0, |
1357
|
|
|
|
|
|
|
right => 0); |
1358
|
|
|
|
|
|
|
# 'scroll-event' class closure, getting Gtk2::Gdk::Event::Scroll |
1359
|
|
|
|
|
|
|
sub _do_scroll_event { |
1360
|
|
|
|
|
|
|
my ($self, $event) = @_; |
1361
|
|
|
|
|
|
|
#### TickerView scroll-event: $event->direction |
1362
|
|
|
|
|
|
|
my $dir = $event->direction; |
1363
|
|
|
|
|
|
|
my $vertical = $self->{'vertical'}; |
1364
|
|
|
|
|
|
|
|
1365
|
|
|
|
|
|
|
# width when horiz, height when vert |
1366
|
|
|
|
|
|
|
my $step = ($self->allocation->values)[2 + $vertical] |
1367
|
|
|
|
|
|
|
* ($event->state & 'control-mask' ? 0.9 : 0.1) |
1368
|
|
|
|
|
|
|
* $direction_sign{$dir}; |
1369
|
|
|
|
|
|
|
unless ($direction_is_vertical{$dir} ^ $vertical) { |
1370
|
|
|
|
|
|
|
if ($self->get_direction eq 'rtl') { $step = - $step; } |
1371
|
|
|
|
|
|
|
} |
1372
|
|
|
|
|
|
|
$self->scroll_pixels ($step); |
1373
|
|
|
|
|
|
|
return $self->signal_chain_from_overridden ($event); |
1374
|
|
|
|
|
|
|
} |
1375
|
|
|
|
|
|
|
|
1376
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
1377
|
|
|
|
|
|
|
# renderer changes |
1378
|
|
|
|
|
|
|
|
1379
|
|
|
|
|
|
|
sub _cellinfo_list_changed { |
1380
|
|
|
|
|
|
|
my ($self) = @_; |
1381
|
|
|
|
|
|
|
### TickerView _cellinfo_list_changed() |
1382
|
|
|
|
|
|
|
%{$self->{'row_widths'}} = (); |
1383
|
|
|
|
|
|
|
_pixmap_queue_draw ($self); |
1384
|
|
|
|
|
|
|
_update_timer ($self); # possible newly empty or non-empty cellinfo list |
1385
|
|
|
|
|
|
|
$self->SUPER::_cellinfo_list_changed; |
1386
|
|
|
|
|
|
|
} |
1387
|
|
|
|
|
|
|
|
1388
|
|
|
|
|
|
|
sub _cellinfo_attributes_changed { |
1389
|
|
|
|
|
|
|
my ($self) = @_; |
1390
|
|
|
|
|
|
|
### TickerView _cellinfo_attributes_changed() |
1391
|
|
|
|
|
|
|
%{$self->{'row_widths'}} = (); |
1392
|
|
|
|
|
|
|
_pixmap_queue_draw ($self); |
1393
|
|
|
|
|
|
|
$self->SUPER::_cellinfo_attributes_changed; |
1394
|
|
|
|
|
|
|
} |
1395
|
|
|
|
|
|
|
|
1396
|
|
|
|
|
|
|
|
1397
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
1398
|
|
|
|
|
|
|
# model changes |
1399
|
|
|
|
|
|
|
# |
1400
|
|
|
|
|
|
|
# The main optimization attempted here is to do nothing when changed rows |
1401
|
|
|
|
|
|
|
# are off-screen, or rather off-pixmap, with the aim of doing no drawing |
1402
|
|
|
|
|
|
|
# when undisplayed parts of the model change. |
1403
|
|
|
|
|
|
|
# |
1404
|
|
|
|
|
|
|
# In practice you have to be in fixed-height-mode for off-screen updates to |
1405
|
|
|
|
|
|
|
# do nothing at all since in the default "all rows sized" mode any change or |
1406
|
|
|
|
|
|
|
# insert or delete has to re-examine all rows. The insert could check only |
1407
|
|
|
|
|
|
|
# for an increase, but doesn't do that currently. |
1408
|
|
|
|
|
|
|
# |
1409
|
|
|
|
|
|
|
|
1410
|
|
|
|
|
|
|
# 'row-changed' on the model |
1411
|
|
|
|
|
|
|
sub _do_row_changed { |
1412
|
|
|
|
|
|
|
my ($model, $path, $iter, $ref_weak_self) = @_; |
1413
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return; |
1414
|
|
|
|
|
|
|
### _do_row_changed() path: $path->to_string |
1415
|
|
|
|
|
|
|
$path->get_depth == 1 || return; # only top rows of the model |
1416
|
|
|
|
|
|
|
my ($index) = $path->get_indices; |
1417
|
|
|
|
|
|
|
|
1418
|
|
|
|
|
|
|
# recalculate width |
1419
|
|
|
|
|
|
|
delete $self->{'row_widths'}->{$index}; |
1420
|
|
|
|
|
|
|
|
1421
|
|
|
|
|
|
|
# fixed-height-mode means every row is the same height so a change to any |
1422
|
|
|
|
|
|
|
# one of them doesn't affect the previously calculated size -- even when |
1423
|
|
|
|
|
|
|
# it's a change to the representative row 0. Believe that's how |
1424
|
|
|
|
|
|
|
# GtkTreeView interprets its fixed-height-mode, and it has the happy |
1425
|
|
|
|
|
|
|
# effect of not provoking repeated rechecks if row 0 changes a lot. |
1426
|
|
|
|
|
|
|
# |
1427
|
|
|
|
|
|
|
# In non fixed-height-mode, however, every row change potentially |
1428
|
|
|
|
|
|
|
# increases or decreases the height. (Or at least a change to the highest |
1429
|
|
|
|
|
|
|
# could decrease, or a change to any of the equal highest could increase.) |
1430
|
|
|
|
|
|
|
# |
1431
|
|
|
|
|
|
|
if (! $self->{'fixed_height_mode'}) { |
1432
|
|
|
|
|
|
|
$self->queue_resize; |
1433
|
|
|
|
|
|
|
} |
1434
|
|
|
|
|
|
|
|
1435
|
|
|
|
|
|
|
# if changed row is in pixmap then redraw |
1436
|
|
|
|
|
|
|
_pixmap_queue_draw_if_index ($self, $index); |
1437
|
|
|
|
|
|
|
} |
1438
|
|
|
|
|
|
|
|
1439
|
|
|
|
|
|
|
# 'row-inserted' on the model |
1440
|
|
|
|
|
|
|
sub _do_row_inserted { |
1441
|
|
|
|
|
|
|
my ($model, $path, $iter, $ref_weak_self) = @_; |
1442
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return; |
1443
|
|
|
|
|
|
|
### _do_row_inserted() path: $path->to_string |
1444
|
|
|
|
|
|
|
$path->get_depth == 1 || return; # only top rows |
1445
|
|
|
|
|
|
|
my ($index) = $path->get_indices; |
1446
|
|
|
|
|
|
|
|
1447
|
|
|
|
|
|
|
if ($self->{'model_empty'}) { |
1448
|
|
|
|
|
|
|
# empty -> non-empty restarts timer if stopped due to empty |
1449
|
|
|
|
|
|
|
_update_timer ($self); |
1450
|
|
|
|
|
|
|
} |
1451
|
|
|
|
|
|
|
if ($self->{'model_empty'} || ! $self->{'fixed_height_mode'}) { |
1452
|
|
|
|
|
|
|
# empty -> non-empty changes size from zero to something; |
1453
|
|
|
|
|
|
|
# and any new row insertion resizes when non fixed-height-mode |
1454
|
|
|
|
|
|
|
# (could just see if this new row bigger than already calculated) |
1455
|
|
|
|
|
|
|
$self->queue_resize; |
1456
|
|
|
|
|
|
|
} |
1457
|
|
|
|
|
|
|
$self->{'model_empty'} = 0; |
1458
|
|
|
|
|
|
|
|
1459
|
|
|
|
|
|
|
_pixmap_queue_draw_if_index ($self, $index); |
1460
|
|
|
|
|
|
|
_apply_remap ($self, |
1461
|
|
|
|
|
|
|
# called as $new_index = $remap->($old_index) |
1462
|
|
|
|
|
|
|
sub { $_[0] >= $index ? $_[0] + 1 : $_[0] }); |
1463
|
|
|
|
|
|
|
} |
1464
|
|
|
|
|
|
|
|
1465
|
|
|
|
|
|
|
# 'row-deleted' on the model |
1466
|
|
|
|
|
|
|
sub _do_row_deleted { |
1467
|
|
|
|
|
|
|
my ($model, $path, $ref_weak_self) = @_; |
1468
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return; |
1469
|
|
|
|
|
|
|
### _do_row_deleted() path: $path->to_string |
1470
|
|
|
|
|
|
|
$path->get_depth == 1 || return; # only top rows |
1471
|
|
|
|
|
|
|
my ($index) = $path->get_indices; |
1472
|
|
|
|
|
|
|
|
1473
|
|
|
|
|
|
|
delete $self->{'row_widths'}->{$index}; |
1474
|
|
|
|
|
|
|
|
1475
|
|
|
|
|
|
|
my $model_empty = $self->{'model_empty'} = ! $model->get_iter_first; |
1476
|
|
|
|
|
|
|
if ($model_empty) { |
1477
|
|
|
|
|
|
|
# becoming empty, stop timer while empty |
1478
|
|
|
|
|
|
|
_update_timer ($self); |
1479
|
|
|
|
|
|
|
} |
1480
|
|
|
|
|
|
|
if ($model_empty || ! $self->{'fixed_height_mode'}) { |
1481
|
|
|
|
|
|
|
# becoming empty will become zero size; |
1482
|
|
|
|
|
|
|
# or if ever row checked then any delete affects height |
1483
|
|
|
|
|
|
|
# (actually only a delete of the highest row affects it, if wanted to |
1484
|
|
|
|
|
|
|
# record which was the biggest) |
1485
|
|
|
|
|
|
|
$self->queue_resize; |
1486
|
|
|
|
|
|
|
} |
1487
|
|
|
|
|
|
|
|
1488
|
|
|
|
|
|
|
# want_index and row_widths move down. |
1489
|
|
|
|
|
|
|
# If want_index itself is deleted then leave it unchanged to show from the |
1490
|
|
|
|
|
|
|
# next following, and if it was the last row then it'll wrap around in the |
1491
|
|
|
|
|
|
|
# draw. |
1492
|
|
|
|
|
|
|
# |
1493
|
|
|
|
|
|
|
_pixmap_queue_draw_if_index ($self, $index); |
1494
|
|
|
|
|
|
|
_apply_remap ($self, |
1495
|
|
|
|
|
|
|
# called as $new_index = $remap->($old_index) |
1496
|
|
|
|
|
|
|
sub { $_[0] > $index ? $_[0] - 1 : $_[0] }); |
1497
|
|
|
|
|
|
|
} |
1498
|
|
|
|
|
|
|
|
1499
|
|
|
|
|
|
|
# 'rows-reordered' signal on the model |
1500
|
|
|
|
|
|
|
sub _do_rows_reordered { |
1501
|
|
|
|
|
|
|
my ($model, $reordered_path, $reordered_iter, $aref, $ref_weak_self) = @_; |
1502
|
|
|
|
|
|
|
my $self = $$ref_weak_self || return; |
1503
|
|
|
|
|
|
|
### _do_rows_reordered() |
1504
|
|
|
|
|
|
|
if (defined $reordered_iter) { return; } # top rows only |
1505
|
|
|
|
|
|
|
|
1506
|
|
|
|
|
|
|
# $oldpos == $aref->[$newpos], ie. aref says where the row used to be. |
1507
|
|
|
|
|
|
|
# $remap{$oldpos} == $newpos, ie. where the old has been sent |
1508
|
|
|
|
|
|
|
# Building a hash might be a bit unnecessary if not much to remap, but |
1509
|
|
|
|
|
|
|
# it's less code than a linear search or similar. |
1510
|
|
|
|
|
|
|
# |
1511
|
|
|
|
|
|
|
my %remap; |
1512
|
|
|
|
|
|
|
@remap{@$aref} = (0 .. $#$aref); |
1513
|
|
|
|
|
|
|
if (DEBUG) { require Data::Dumper; |
1514
|
|
|
|
|
|
|
print " remap ", |
1515
|
|
|
|
|
|
|
Data::Dumper->new([\%remap],['remap'])->Sortkeys(1)->Dump; } |
1516
|
|
|
|
|
|
|
# want_index and row_widths permute |
1517
|
|
|
|
|
|
|
# allow for indexes out of range in case want_index yet unnormalized |
1518
|
|
|
|
|
|
|
# called as $new_index = $remap->($old_index) |
1519
|
|
|
|
|
|
|
my $remap = sub { defined $remap{$_[0]} ? $remap{$_[0]} : $_[0] }; |
1520
|
|
|
|
|
|
|
|
1521
|
|
|
|
|
|
|
# Set the pixmap to redraw if the drawn rows are no longer a contiguous |
1522
|
|
|
|
|
|
|
# run, modulo the model length. This is a bit of work to check, but the |
1523
|
|
|
|
|
|
|
# pixmap contents can be retained nicely under rotations or shuffle ups or |
1524
|
|
|
|
|
|
|
# downs that don't affect the displayed portion. |
1525
|
|
|
|
|
|
|
my $len = scalar @$aref; |
1526
|
|
|
|
|
|
|
my $delta_func = sub { ($_[0] - $remap->($_[0]) + $len) % $len }; |
1527
|
|
|
|
|
|
|
my $drawn = $self->{'drawn_array'}; |
1528
|
|
|
|
|
|
|
if (@$drawn) { |
1529
|
|
|
|
|
|
|
my $want_delta = $delta_func->($drawn->[0]); |
1530
|
|
|
|
|
|
|
for (my $i = 2; $i < @$drawn; $i += 2) { |
1531
|
|
|
|
|
|
|
if ($delta_func->($drawn->[$i]) != $want_delta) { |
1532
|
|
|
|
|
|
|
_pixmap_queue_draw ($self); # shuffled about, must redraw |
1533
|
|
|
|
|
|
|
last; |
1534
|
|
|
|
|
|
|
} |
1535
|
|
|
|
|
|
|
} |
1536
|
|
|
|
|
|
|
} |
1537
|
|
|
|
|
|
|
_apply_remap ($self, $remap); |
1538
|
|
|
|
|
|
|
} |
1539
|
|
|
|
|
|
|
|
1540
|
|
|
|
|
|
|
# set the pixmap to redraw if $index is drawn in it |
1541
|
|
|
|
|
|
|
sub _pixmap_queue_draw_if_index { |
1542
|
|
|
|
|
|
|
my ($self, $index) = @_; |
1543
|
|
|
|
|
|
|
my $drawn = $self->{'drawn_array'}; |
1544
|
|
|
|
|
|
|
for (my $i = 0; $i < @$drawn; $i += 2) { |
1545
|
|
|
|
|
|
|
if ($drawn->[$i] == $index) { |
1546
|
|
|
|
|
|
|
_pixmap_queue_draw ($self); |
1547
|
|
|
|
|
|
|
return 1; |
1548
|
|
|
|
|
|
|
} |
1549
|
|
|
|
|
|
|
} |
1550
|
|
|
|
|
|
|
return 0; |
1551
|
|
|
|
|
|
|
} |
1552
|
|
|
|
|
|
|
|
1553
|
|
|
|
|
|
|
# Remap indexes in want_index, drawn_array, and row_widths. drawn_array is |
1554
|
|
|
|
|
|
|
# empty at this point if it's going to be redrawn |
1555
|
|
|
|
|
|
|
sub _apply_remap { |
1556
|
|
|
|
|
|
|
my ($self, $remap) = @_; |
1557
|
|
|
|
|
|
|
### _apply_remap(): $remap |
1558
|
|
|
|
|
|
|
|
1559
|
|
|
|
|
|
|
if (defined (my $want_index = $self->{'want_index'})) { |
1560
|
|
|
|
|
|
|
if (DEBUG) { print " want_index $want_index to ", |
1561
|
|
|
|
|
|
|
$remap->($want_index),"\n"; } |
1562
|
|
|
|
|
|
|
$self->{'want_index'} = $remap->($want_index); |
1563
|
|
|
|
|
|
|
} |
1564
|
|
|
|
|
|
|
my $drawn = $self->{'drawn_array'}; |
1565
|
|
|
|
|
|
|
if (@$drawn) { |
1566
|
|
|
|
|
|
|
for (my $i = 0; $i < @$drawn; $i += 2) { |
1567
|
|
|
|
|
|
|
$drawn->[$i] = $remap->($drawn->[$i]); |
1568
|
|
|
|
|
|
|
} |
1569
|
|
|
|
|
|
|
} |
1570
|
|
|
|
|
|
|
_hash_keys_remap ($self->{'row_widths'}, $remap); |
1571
|
|
|
|
|
|
|
} |
1572
|
|
|
|
|
|
|
|
1573
|
|
|
|
|
|
|
# modify the keys in %$href by $newkey = $func->($oldkey) |
1574
|
|
|
|
|
|
|
# the value associated with $oldkey moves to $newkey |
1575
|
|
|
|
|
|
|
# |
1576
|
|
|
|
|
|
|
sub _hash_keys_remap { |
1577
|
|
|
|
|
|
|
my ($href, $func) = @_; |
1578
|
|
|
|
|
|
|
%$href = map { ($func->($_), $href->{$_}) } keys %$href; |
1579
|
|
|
|
|
|
|
} |
1580
|
|
|
|
|
|
|
|
1581
|
|
|
|
|
|
|
|
1582
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
1583
|
|
|
|
|
|
|
# generic helpers |
1584
|
|
|
|
|
|
|
|
1585
|
|
|
|
|
|
|
# _make_all_zeros_proc() returns a procedure to be called |
1586
|
|
|
|
|
|
|
# $func->($index,$width) designed to protect against every $index having a |
1587
|
|
|
|
|
|
|
# zero $width. |
1588
|
|
|
|
|
|
|
# |
1589
|
|
|
|
|
|
|
# $func returns true until it sees an $index==0 and then a second $index==0, |
1590
|
|
|
|
|
|
|
# with all calls having $width==0. The idea is that if the drawing, |
1591
|
|
|
|
|
|
|
# scrolling or whatever loop has gone from $index zero all the way up and |
1592
|
|
|
|
|
|
|
# around back to $index zero again, and all the $width's seen are zero, then |
1593
|
|
|
|
|
|
|
# it should bail out. |
1594
|
|
|
|
|
|
|
# |
1595
|
|
|
|
|
|
|
# Any non-zero $width seen makes the returned procedure always return true. |
1596
|
|
|
|
|
|
|
# It might be only a single index position out of thousands, but that's |
1597
|
|
|
|
|
|
|
# enough. |
1598
|
|
|
|
|
|
|
# |
1599
|
|
|
|
|
|
|
sub _make_all_zeros_proc { |
1600
|
|
|
|
|
|
|
my $seen_nonzero = 0; |
1601
|
|
|
|
|
|
|
my $count_index_zero = 0; |
1602
|
|
|
|
|
|
|
return sub { |
1603
|
|
|
|
|
|
|
my ($index, $width) = @_; |
1604
|
|
|
|
|
|
|
if ($width != 0) { $seen_nonzero = 1; } |
1605
|
|
|
|
|
|
|
if ($index == 0) { $count_index_zero++; } |
1606
|
|
|
|
|
|
|
return (! $seen_nonzero) && ($count_index_zero >= 2); |
1607
|
|
|
|
|
|
|
} |
1608
|
|
|
|
|
|
|
} |
1609
|
|
|
|
|
|
|
|
1610
|
|
|
|
|
|
|
|
1611
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
1612
|
|
|
|
|
|
|
# other method funcs |
1613
|
|
|
|
|
|
|
|
1614
|
|
|
|
|
|
|
sub get_path_at_pos { |
1615
|
|
|
|
|
|
|
my ($self, $x, $y) = @_; |
1616
|
|
|
|
|
|
|
### get_path_at_pos(): "$x,$y" |
1617
|
|
|
|
|
|
|
|
1618
|
|
|
|
|
|
|
# Go from the want_x/want_index desired position, even if the drawing |
1619
|
|
|
|
|
|
|
# isn't yet actually displaying that. This makes most sense after a |
1620
|
|
|
|
|
|
|
# programmatic scroll, and if it's a user button press then the display |
1621
|
|
|
|
|
|
|
# will only be a moment away from showing that want_x/want_index position. |
1622
|
|
|
|
|
|
|
# |
1623
|
|
|
|
|
|
|
my $index = $self->{'want_index'}; |
1624
|
|
|
|
|
|
|
$x -= $self->{'want_x'}; |
1625
|
|
|
|
|
|
|
if (DEBUG) { print " adj for want_x=",$self->{'want_x'},", to x=$x\n"; } |
1626
|
|
|
|
|
|
|
|
1627
|
|
|
|
|
|
|
($x, $index) = _normalize ($self, -$x, $index); |
1628
|
|
|
|
|
|
|
if (DEBUG) { print " got ", (defined $x ? $x : 'undef'), |
1629
|
|
|
|
|
|
|
",",(defined $index ? $index : 'undef'),"\n"; } |
1630
|
|
|
|
|
|
|
if (defined $x) { |
1631
|
|
|
|
|
|
|
return Gtk2::TreePath->new_from_indices ($index); |
1632
|
|
|
|
|
|
|
} else { |
1633
|
|
|
|
|
|
|
return undef; |
1634
|
|
|
|
|
|
|
} |
1635
|
|
|
|
|
|
|
} |
1636
|
|
|
|
|
|
|
|
1637
|
|
|
|
|
|
|
1; |
1638
|
|
|
|
|
|
|
__END__ |