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