line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# $Id: TDFinder.pm,v 1.17 2005/01/17 03:04:43 reid Exp $ |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# TDFinder: find players in TDLIST and enter them into |
4
|
|
|
|
|
|
|
# an appropriate .tde file. The most recent |
5
|
|
|
|
|
|
|
# TDLIST is available from the AGA at: |
6
|
|
|
|
|
|
|
# http:www.usgo.org |
7
|
|
|
|
|
|
|
# Copyright (C) 2004, 2005 Reid Augustin reid@netchip.com |
8
|
|
|
|
|
|
|
# 1000 San Mateo Dr. |
9
|
|
|
|
|
|
|
# Menlo Park, CA 94025 USA |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# This library is free software; you can redistribute it and/or modify it |
12
|
|
|
|
|
|
|
# under the same terms as Perl itself, either Perl version 5.8.5 or, at your |
13
|
|
|
|
|
|
|
# option, any later version of Perl 5 you may have available. |
14
|
|
|
|
|
|
|
# |
15
|
|
|
|
|
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT |
16
|
|
|
|
|
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
17
|
|
|
|
|
|
|
# FITNESS FOR A PARTICULAR PURPOSE. |
18
|
|
|
|
|
|
|
# |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# |
21
|
|
|
|
|
|
|
# ToDo: |
22
|
|
|
|
|
|
|
# double click match to copy into tdeText |
23
|
|
|
|
|
|
|
# drag matches into tdeText |
24
|
|
|
|
|
|
|
# add menu button with: |
25
|
|
|
|
|
|
|
# help, sort options, |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 NAME |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
TDFinder - a widget to support preparing Go tournament registration |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head1 SYNOPSIS |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
use Games::Go::TDFinder; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
$tdFinder = $parent-EGames::Go::TDFinder ( ? options ? ); |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=head1 DESCRIPTION |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
TDFinder is a widget to assist in preparing a Go Tournament register.tde file in AGA (American |
40
|
|
|
|
|
|
|
Go Association) format. It consists of three main parts: a TDEntry widget at the bottom, a |
41
|
|
|
|
|
|
|
'match' list in the middle (which is an ROText widget), and the tde information at the top (A |
42
|
|
|
|
|
|
|
TextUndo widget). |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
The widget opens the TDLIST file for searching. Tournemant directors should download the most |
45
|
|
|
|
|
|
|
recent TDLIST from the AGA shortly before the tournament. The most recent TDLIST is available |
46
|
|
|
|
|
|
|
from the AGA at: L |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
Typing search keys into the TDEntry field causes the TDFinder to search through the TDLIST |
49
|
|
|
|
|
|
|
looking for matches. When the number of matches is small enough to fit into the 'match' list |
50
|
|
|
|
|
|
|
ROText widget, they are posted there. Individual TDLIST entries can be selected either by |
51
|
|
|
|
|
|
|
further refining the search keys, or by using the Up/Down arrow keys. Typing 'Enter', double |
52
|
|
|
|
|
|
|
clicking a match (BUGBUG: TBD), or dragging a match to the tde text widget (BUGBUG: TBD) |
53
|
|
|
|
|
|
|
transfers a match to the tde file. |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
The caller is responsible for make sure the final register.tde file corresponds to the |
56
|
|
|
|
|
|
|
information in the tde part of the TDFinder widget. |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=cut |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
package Games::Go::TDFinder; # composite widget for finding entries in TDLIST from AGA |
61
|
|
|
|
|
|
|
|
62
|
1
|
|
|
1
|
|
26737
|
use 5.005; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
36
|
|
63
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
39
|
|
64
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
42
|
|
65
|
1
|
|
|
1
|
|
1062
|
use IO::File; |
|
1
|
|
|
|
|
16044
|
|
|
1
|
|
|
|
|
149
|
|
66
|
1
|
|
|
1
|
|
1197
|
use File::stat; # stat fields by name |
|
1
|
|
|
|
|
13018
|
|
|
1
|
|
|
|
|
10
|
|
67
|
1
|
|
|
1
|
|
1465
|
use Games::Go::AGATourn; |
|
1
|
|
|
|
|
8780
|
|
|
1
|
|
|
|
|
79
|
|
68
|
1
|
|
|
1
|
|
501
|
use Tk; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
use Tk::widgets qw/ Entry TextUndo ROText Adjuster /; |
70
|
|
|
|
|
|
|
use Games::Go::TDEntry; |
71
|
|
|
|
|
|
|
use Carp; |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
use base qw(Tk::Frame); # TDFinder is a composite widget |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Construct Tk::Widget 'TDFinder'; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
BEGIN { |
78
|
|
|
|
|
|
|
our $VERSION = sprintf "%d.%03d", '$Revision: 1.17 $' =~ /(\d+)/g; |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# class variables: |
82
|
|
|
|
|
|
|
our (@tdList); # there should be one and only one TDLIST file |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
###################################################### |
85
|
|
|
|
|
|
|
# |
86
|
|
|
|
|
|
|
# methods |
87
|
|
|
|
|
|
|
# |
88
|
|
|
|
|
|
|
##################################################### |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub ClassInit { |
91
|
|
|
|
|
|
|
my ($class, $mw) = @_; |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
$class->SUPER::ClassInit($mw); |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub Populate { |
97
|
|
|
|
|
|
|
my ($self, $args) = @_; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
$self->SUPER::Populate($args); |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
$self->_initTDFinder(); |
102
|
|
|
|
|
|
|
$self->ConfigSpecs( |
103
|
|
|
|
|
|
|
-tdListFile => ['PASSIVE', 'tdListFile', 'TDListFile', 'tdlist' ], |
104
|
|
|
|
|
|
|
-height => [$self->{matchText}, 'height', 'Height', 12 ], |
105
|
|
|
|
|
|
|
-scrollbars => [$self->{tdeText}, 'scrollbars', 'Scrollbars', 'osow' ], |
106
|
|
|
|
|
|
|
-namelength => ['PASSIVE', 'namelength', 'Namelength', 20 ], |
107
|
|
|
|
|
|
|
-clublength => ['PASSIVE', 'clublength', 'Clublength', 10 ], |
108
|
|
|
|
|
|
|
DEFAULT => [$self->{tdeText}], |
109
|
|
|
|
|
|
|
); |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=head1 OPTIONS |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=over 4 |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=item B<-tdListFile> => filename |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
Specify the filename of the current TDLIST file of players (from the AGA). If |
118
|
|
|
|
|
|
|
B<-tdListFile> => undef, no TDLIST file is opened (and you can't really do much of |
119
|
|
|
|
|
|
|
anything), otherwise if TDLIST can't be opened, TDFinder complains and dies. |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
TDFinder checks the date of the tdListFile. If it is less than two weeks old, |
122
|
|
|
|
|
|
|
TDFinder presents a warning dialog box. |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
B<-tdListFile> may only be specified at widget creation. Configuring it later |
125
|
|
|
|
|
|
|
has no effect. |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
Default: 'tdlist' (in the current directory - a symlink is acceptable) |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=item B<-height> => height in chars |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Height is passed to the matchText widget. |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
Default: 12 |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=item B<-scrollbars> => a scrollbar 'where string |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
The scrollbar 'where' string is passed to the tdeText widget. See the |
138
|
|
|
|
|
|
|
B<-scrollbars> option in L for details. |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
Default: 'osow' |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=item B<-namelength> => number |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
The starting length of names in the tdeText widget. Lines are formatted so |
145
|
|
|
|
|
|
|
that all the names take the same amount of space. This number grows if a |
146
|
|
|
|
|
|
|
longer name is entered into tdeText. |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
Default: 20 |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=item B<-clublength> => number |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
The starting length of club names in the tdeText widget. Lines are formatted |
153
|
|
|
|
|
|
|
so that all the club names take the same amount of space. This number grows |
154
|
|
|
|
|
|
|
if a longer name is entered into tdeText. |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
Default: 10 |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=item B |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
All other options are passed to the tdeText widget. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=back |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=cut |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
$self->Delegates(DEFAULT => $self->{tdeText}); # all unknown methods |
167
|
|
|
|
|
|
|
$self->toplevel->withdraw; |
168
|
|
|
|
|
|
|
$self->_initTdList($args); |
169
|
|
|
|
|
|
|
$self->toplevel->deiconify; |
170
|
|
|
|
|
|
|
return($self); |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
###################################################### |
174
|
|
|
|
|
|
|
# |
175
|
|
|
|
|
|
|
# Private methods |
176
|
|
|
|
|
|
|
# |
177
|
|
|
|
|
|
|
##################################################### |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
sub _initTDFinder { |
180
|
|
|
|
|
|
|
my $self = shift; |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# an undo-able Text widget for the register.tde file |
183
|
|
|
|
|
|
|
my $t = $self->{tdeText} = $self->Scrolled( |
184
|
|
|
|
|
|
|
'TextUndo', |
185
|
|
|
|
|
|
|
-wrap => 'word', |
186
|
|
|
|
|
|
|
-exportselection => 'true', ); |
187
|
|
|
|
|
|
|
$t->delete('1.0', 'end'); |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
$t->bind('Tk::TextUndo', '', [ 'undo']); |
190
|
|
|
|
|
|
|
$t->bind('Tk::TextUndo', '', [ 'redo']); |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# a read-only Text widget to show list of matches |
193
|
|
|
|
|
|
|
my $m = $self->{matchText} = $self->ROText( |
194
|
|
|
|
|
|
|
-wrap => 'word', |
195
|
|
|
|
|
|
|
-takefocus => 0, |
196
|
|
|
|
|
|
|
-exportselection => 'true', ); |
197
|
|
|
|
|
|
|
my $a = $self->Adjuster(); |
198
|
|
|
|
|
|
|
# TDEntry widget for entering search keys |
199
|
|
|
|
|
|
|
$self->{tdEntry} = $self->TDEntry(-text => 'Search:'); |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# pack all the widgets |
202
|
|
|
|
|
|
|
$self->{tdEntry}->pack( |
203
|
|
|
|
|
|
|
-side => 'bottom', |
204
|
|
|
|
|
|
|
-expand => 'false', |
205
|
|
|
|
|
|
|
-fill => 'x'); |
206
|
|
|
|
|
|
|
$m->pack( |
207
|
|
|
|
|
|
|
-side => 'bottom', |
208
|
|
|
|
|
|
|
-expand => 'true', |
209
|
|
|
|
|
|
|
-fill => 'both'); |
210
|
|
|
|
|
|
|
$a->packAfter($m, |
211
|
|
|
|
|
|
|
-side => 'bottom', |
212
|
|
|
|
|
|
|
-expand => 'true', |
213
|
|
|
|
|
|
|
-fill => 'both'); |
214
|
|
|
|
|
|
|
$t->pack( |
215
|
|
|
|
|
|
|
-side => 'bottom', |
216
|
|
|
|
|
|
|
-expand => 'true', |
217
|
|
|
|
|
|
|
-fill => 'both'); |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
# bindings: |
220
|
|
|
|
|
|
|
my $e = $self->{entry} = $self->{tdEntry}->Subwidget('entry'); |
221
|
|
|
|
|
|
|
$e->bind('' => [$self => '_entryKeyPress', Ev('A'), ]); # new key in search field |
222
|
|
|
|
|
|
|
$e->bind('' => [$self => '_moveListSelection', -1]); |
223
|
|
|
|
|
|
|
$e->bind('' => [$self => '_moveListSelection', +1]); |
224
|
|
|
|
|
|
|
$e->bind('' => [$self => '_changeAgaRating', +1]); |
225
|
|
|
|
|
|
|
$e->bind('' => [$self => '_changeAgaRating', -1]); |
226
|
|
|
|
|
|
|
$e->bind('' => [$self => '_addMatchSelection']); |
227
|
|
|
|
|
|
|
$e->bind('' => [$self => '_Escape']); |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
$m->tagConfigure("match", |
230
|
|
|
|
|
|
|
-background => 'lightblue', |
231
|
|
|
|
|
|
|
-relief => 'raised', |
232
|
|
|
|
|
|
|
-underline => 'true'); |
233
|
|
|
|
|
|
|
$t->tagConfigure('dup', |
234
|
|
|
|
|
|
|
-foreground => 'red'); |
235
|
|
|
|
|
|
|
$m->tagConfigure('dup', |
236
|
|
|
|
|
|
|
-foreground => 'red'); |
237
|
|
|
|
|
|
|
$self->Advertise(entry => $e); |
238
|
|
|
|
|
|
|
$self->Advertise(tdeText => $t); |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=head1 ADVERTISED WIDGETS |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=over 4 |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=item B |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
The TDEntry support widget: consists of a label, an entry widget, and a 'Case sensitive' |
247
|
|
|
|
|
|
|
Checkbutton. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
You might want to do something like: |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
$tdFinder->Subwidget('entry')->focus(); # start with focus in entry widget. |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=item B |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
The TextUndo widget which holds the current register.tde contents. The caller is |
256
|
|
|
|
|
|
|
reponsible for maintaining the on-disk file contents and making sure the tdeText content |
257
|
|
|
|
|
|
|
matches the register.tde file (see L(1)). |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
Use something like: |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
$register_tde = tdFinder->Subwidget('tdeText')->get('1.0', 'end') |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
to get the current contents of the tdeText widget. |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=back |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=cut |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
$self->{mostRecentInsert} = 'none'; |
270
|
|
|
|
|
|
|
$self->{matchForeground} = $self->{matchText}->cget('-foreground'); |
271
|
|
|
|
|
|
|
$self->{tdeForeground} = $self->{tdeText}->cget('-foreground'); |
272
|
|
|
|
|
|
|
$self->{agaTourn} = Games::Go::AGATourn->new(register_tde => undef, |
273
|
|
|
|
|
|
|
Round => 0); |
274
|
|
|
|
|
|
|
my $height = $m->reqheight - (2 * $m->cget('-pady')); # pixel height |
275
|
|
|
|
|
|
|
$self->{matchFontHeight} = int($height / $self->{matchText}->cget('-height')); # div by lines |
276
|
|
|
|
|
|
|
# initialize: |
277
|
|
|
|
|
|
|
$self->clear; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
sub _initTdList { |
281
|
|
|
|
|
|
|
my ($self, $args) = @_; |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
unless (scalar @tdList) { # init class data once only |
284
|
|
|
|
|
|
|
my $tdListFile = exists($args->{'-tdListFile'}) ? $args->{'-tdListFile'} : 'tdlist'; |
285
|
|
|
|
|
|
|
if (defined($tdListFile)) { |
286
|
|
|
|
|
|
|
my $fd = IO::File->new("<$tdListFile") or croak "can't open TDLIST $tdListFile: $!\n"; |
287
|
|
|
|
|
|
|
$self->_checkTime($tdListFile); |
288
|
|
|
|
|
|
|
while (<$fd>) { |
289
|
|
|
|
|
|
|
push (@tdList, $_); |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
close($fd); |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
$self->_clearListSelection; # fake a keypress to get TDLIST count into match window |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
sub _checkTime { |
298
|
|
|
|
|
|
|
my ($self, $file, @args) = @_; |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
if (-f $file) { # seems to follow symbolic links just fine... |
301
|
|
|
|
|
|
|
my $week = 60 * 60 * 24 * 7; # seconds in a week |
302
|
|
|
|
|
|
|
if (stat($file)->mtime < time - (2 * $week)) { # too old? |
303
|
|
|
|
|
|
|
my $rsp = $self->Dialog( |
304
|
|
|
|
|
|
|
-text => "$file is more than two weeks old.\n\n" . |
305
|
|
|
|
|
|
|
"Please get the most recent TDListN.txt file from the AGA at:\n\n" . |
306
|
|
|
|
|
|
|
" http://usgo.org/ratings/default.asp\n", |
307
|
|
|
|
|
|
|
-buttons => ['Quit', 'Continue'], |
308
|
|
|
|
|
|
|
-default_button => 'Quit', |
309
|
|
|
|
|
|
|
)->Show; |
310
|
|
|
|
|
|
|
&Tk::exit(1) if ($rsp eq 'Quit'); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} else { |
313
|
|
|
|
|
|
|
croak ("Don't know how to handle $file - doesn't seem to be a regular file\n"); |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
sub _getDupKeys { |
318
|
|
|
|
|
|
|
my $self = shift; |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
my $t = $self->{tdeText}; |
321
|
|
|
|
|
|
|
my @lines = ('dummy', split ("\n", $t->get("1.0", "end"))); # dummy line in front |
322
|
|
|
|
|
|
|
$self->{pids} = {}; |
323
|
|
|
|
|
|
|
$self->{names} = {}; |
324
|
|
|
|
|
|
|
for (my $ii = 1; $ii < @lines; $ii++) { |
325
|
|
|
|
|
|
|
$lines[$ii] =~ s/^\s*#.*//s; # filter out comment only lines |
326
|
|
|
|
|
|
|
$lines[$ii] =~ s/^\s*//s; # filter out empty lines |
327
|
|
|
|
|
|
|
next if ($lines[$ii] eq ''); |
328
|
|
|
|
|
|
|
my $p = $self->{agaTourn}->ParseRegisterLine($lines[$ii]); |
329
|
|
|
|
|
|
|
my $pid = lc("$p->{country}$p->{agaNum}"); |
330
|
|
|
|
|
|
|
my $name = lc($p->{name}); # lower case name to create key |
331
|
|
|
|
|
|
|
$name =~ s/\s//g; # and remove all whitespace |
332
|
|
|
|
|
|
|
push (@{$self->{pids}{$pid}}, $ii); |
333
|
|
|
|
|
|
|
push (@{$self->{names}{$name}}, $ii); |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
sub _markTdeDups { |
338
|
|
|
|
|
|
|
my $self = shift; |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
my $t = $self->{tdeText}; |
341
|
|
|
|
|
|
|
my @lines = ('dummy', split ("\n", $t->get("1.0", "end"))); # dummy line in front |
342
|
|
|
|
|
|
|
$self->_getDupKeys(); |
343
|
|
|
|
|
|
|
$t->tagDelete('dup'); # remove all previous duplicate tags |
344
|
|
|
|
|
|
|
foreach my $pid (keys(%{$self->{pids}})) { |
345
|
|
|
|
|
|
|
if (scalar(@{$self->{pids}{$pid}}) > 1) { |
346
|
|
|
|
|
|
|
# uh oh, a duplicate: |
347
|
|
|
|
|
|
|
foreach my $ii (@{$self->{pids}{$pid}}) { |
348
|
|
|
|
|
|
|
$t->tagAdd('dup', "$ii.0", "$ii.8"); |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
foreach my $name (keys(%{$self->{names}})) { |
353
|
|
|
|
|
|
|
if (scalar(@{$self->{names}{$name}}) > 1) { |
354
|
|
|
|
|
|
|
# uh oh, a duplicate: |
355
|
|
|
|
|
|
|
foreach my $ii (@{$self->{names}{$name}}) { |
356
|
|
|
|
|
|
|
$t->tagAdd('dup', "$ii.9", "$ii.9 + " . $self->cget('-namelength') . " chars"); |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
$t->tagConfigure('dup', |
361
|
|
|
|
|
|
|
-foreground => 'red'); |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
sub _rankCompare { |
365
|
|
|
|
|
|
|
my $self = shift; |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
my $ratingA = ($a->{agaRating}); |
368
|
|
|
|
|
|
|
$ratingA = -99 unless (defined($ratingA)); |
369
|
|
|
|
|
|
|
my $ratingB = ($b->{agaRating}); |
370
|
|
|
|
|
|
|
$ratingB = -99 unless (defined($ratingB)); |
371
|
|
|
|
|
|
|
my $d = ($ratingB <=> $ratingA); # reverse order to put stronger players at the top of the list |
372
|
|
|
|
|
|
|
my $s = 'R'; |
373
|
|
|
|
|
|
|
if ($d == 0) { |
374
|
|
|
|
|
|
|
$s = 'n'; |
375
|
|
|
|
|
|
|
$d = ($a->{name} cmp $b->{name}); |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
# my $nameLen = 25; |
378
|
|
|
|
|
|
|
# printf("%-*s %-5s %s$s %5s %*s\n", |
379
|
|
|
|
|
|
|
# $nameLen, $a->{name}, $a->{agaRating}, |
380
|
|
|
|
|
|
|
# ($d > 0) ? '>' : (($d < 0) ? '<' : '='), |
381
|
|
|
|
|
|
|
# $b->{agaRating}, $nameLen, $b->{name},); |
382
|
|
|
|
|
|
|
return $d; |
383
|
|
|
|
|
|
|
} |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
sub _Escape { |
386
|
|
|
|
|
|
|
my ($self) = @_; |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
$self->sort(); |
389
|
|
|
|
|
|
|
$self->_clearListSelection(); |
390
|
|
|
|
|
|
|
} |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
sub _clearListSelection { |
393
|
|
|
|
|
|
|
my ($self) = @_; |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
$self->{entry}->delete(0, 'end'); |
396
|
|
|
|
|
|
|
$self->_entryKeyPress('x'); # fake a key press |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
sub _parseTdListLine { |
400
|
|
|
|
|
|
|
my ($self, $td) = @_; |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
my $p = ($self->{agaTourn}->ParseTdListLine($td)); |
403
|
|
|
|
|
|
|
# convert from TDLIST format to TDE format |
404
|
|
|
|
|
|
|
$p->{comment} = join(' ', $p->{memType}, $p->{expire}, $p->{state}); |
405
|
|
|
|
|
|
|
$p->{comment} =~ s/ */ /g; |
406
|
|
|
|
|
|
|
delete($p->{memType}); |
407
|
|
|
|
|
|
|
delete($p->{expire}); |
408
|
|
|
|
|
|
|
delete($p->{state}); |
409
|
|
|
|
|
|
|
return $p |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
sub _parseRegisterLine { |
413
|
|
|
|
|
|
|
my ($self, $tde) = @_; |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
return $self->{agaTourn}->ParseRegisterLine($tde); |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
sub _addMatchSelection { |
419
|
|
|
|
|
|
|
my ($self) = @_; |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
my $m = $self->{matchText}; # the match text widget |
422
|
|
|
|
|
|
|
if ($self->{matchListValid} > 0) { # add the activated line to TDE |
423
|
|
|
|
|
|
|
$self->addPlayer($self->{matches}[$self->{active} - 1]); |
424
|
|
|
|
|
|
|
} elsif ($self->{matchListValid} < 0) { # no matches, a tmp player? |
425
|
|
|
|
|
|
|
my $entry = $self->{tdEntry}->get(); |
426
|
|
|
|
|
|
|
my ($rank, $name); |
427
|
|
|
|
|
|
|
if ($entry =~ s/\s+([0-9]+[dkDK])\s*$//) { |
428
|
|
|
|
|
|
|
$rank = $1; |
429
|
|
|
|
|
|
|
} else { |
430
|
|
|
|
|
|
|
$m->delete('1.0', 'end'); |
431
|
|
|
|
|
|
|
$m->insert('1.0', 'Unlisted player needs rank (like 3D or 4k) at the end'); |
432
|
|
|
|
|
|
|
return; |
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
if ($entry =~ m/\s*(.+,.*)/) { |
435
|
|
|
|
|
|
|
$name = $1; |
436
|
|
|
|
|
|
|
} else { |
437
|
|
|
|
|
|
|
$m->delete('1.0', 'end'); |
438
|
|
|
|
|
|
|
$m->insert('1.0', 'Unlisted player name needs last name, comma, then first name (and optional middle/honorific, etc).'); |
439
|
|
|
|
|
|
|
return; |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
# cannonicalize the hash key |
442
|
|
|
|
|
|
|
$name =~ s/\s+/ /g; # turn all whitespace into single space |
443
|
|
|
|
|
|
|
$name =~ s/^\s*//; # delete preceding whitespace |
444
|
|
|
|
|
|
|
$name =~ s/\s*$//; # delete following whitespace |
445
|
|
|
|
|
|
|
$self->{tmpNum}++; |
446
|
|
|
|
|
|
|
$self->addTDE("TMP$self->{tmpNum} $name $rank"); |
447
|
|
|
|
|
|
|
} # else - need to narrow the search more, ignore... |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
sub _moveListSelection { |
451
|
|
|
|
|
|
|
my ($self, $change) = @_; |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
my $m = $self->{matchText}; |
454
|
|
|
|
|
|
|
my $active = $self->{active} + $change; |
455
|
|
|
|
|
|
|
return if (($active < 1) or ($active >= $m->index('end') - 1)); |
456
|
|
|
|
|
|
|
$self->{active} = $active; |
457
|
|
|
|
|
|
|
$m->tagRemove('match', '1.0', 'end'); |
458
|
|
|
|
|
|
|
$m->tagAdd('match', "$active.0", "$active.0 lineend"); |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
sub _changeAgaRating { |
462
|
|
|
|
|
|
|
my ($self, $change) = @_; |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
my $m = $self->{matchText}; |
465
|
|
|
|
|
|
|
my $t = $self->{tdeEntry}; |
466
|
|
|
|
|
|
|
my $active = $self->{active}; |
467
|
|
|
|
|
|
|
my $p = $self->{matches}[$active - 1]; |
468
|
|
|
|
|
|
|
my $pid = "$p->{country}$p->{agaNum}"; |
469
|
|
|
|
|
|
|
if (($self->{ratingChanged}{$pid} == -99) and ($change > 0)) { |
470
|
|
|
|
|
|
|
$self->{ratingChanged}{$pid} = -31; # change 99k to 30k |
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
$self->{ratingChanged}{$pid} += $change; |
473
|
|
|
|
|
|
|
if ($self->{ratingChanged}{$pid} == 0) { |
474
|
|
|
|
|
|
|
$self->{ratingChanged}{$pid} += $change; # skip over 0 |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
$m->delete("$active.0", "$active.0 lineend"); |
477
|
|
|
|
|
|
|
$m->insert("$active.0", $self->_format($p)); |
478
|
|
|
|
|
|
|
$m->tagAdd('match', "$active.0", "$active.0 lineend"); |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
sub _entryKeyPress { |
482
|
|
|
|
|
|
|
my ($self, $char) = @_; |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
my $m = $self->{matchText}; |
485
|
|
|
|
|
|
|
$char =~ s/\s*//g; # turn whitespace chars to nothing |
486
|
|
|
|
|
|
|
return if ($char eq ''); # ignore whitespace and control type chars |
487
|
|
|
|
|
|
|
my $width = $m->reqwidth; # insert changes widget back to it's original size. |
488
|
|
|
|
|
|
|
my $height = $m->reqheight; |
489
|
|
|
|
|
|
|
my $lines = $m->cget('-height'); |
490
|
|
|
|
|
|
|
# print("lines=$lines, height=$height, "); |
491
|
|
|
|
|
|
|
$lines = int($height / $self->{matchFontHeight}); |
492
|
|
|
|
|
|
|
# print("new lines=$lines\n"); |
493
|
|
|
|
|
|
|
$m->configure('-height', $lines); |
494
|
|
|
|
|
|
|
$self->{matchListValid} = 0; |
495
|
|
|
|
|
|
|
$m->delete('1.0', 'end'); |
496
|
|
|
|
|
|
|
$m->configure(-foreground => $self->{matchForeground}); |
497
|
|
|
|
|
|
|
my $srchString = $self->{tdEntry}->get(); |
498
|
|
|
|
|
|
|
$srchString =~ s/^\s*//; |
499
|
|
|
|
|
|
|
if ($srchString eq '') { |
500
|
|
|
|
|
|
|
$m->insert('end', scalar(@tdList) . " players in TDLIST\n"); |
501
|
|
|
|
|
|
|
} else { |
502
|
|
|
|
|
|
|
my $matches = $self->{matches} = $self->_search($srchString); |
503
|
|
|
|
|
|
|
if (@$matches == 0) { |
504
|
|
|
|
|
|
|
$m->configure(-foreground => 'red'); |
505
|
|
|
|
|
|
|
if (scalar(@tdList)) { |
506
|
|
|
|
|
|
|
$m->insert('end', "No matches\n"); |
507
|
|
|
|
|
|
|
} else { |
508
|
|
|
|
|
|
|
$m->insert('end', "No TDLIST\n"); |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
$self->{matchListValid} = -1; |
511
|
|
|
|
|
|
|
} elsif (@$matches >= $lines) { |
512
|
|
|
|
|
|
|
$m->insert('end', scalar(@$matches) . " matches\n"); |
513
|
|
|
|
|
|
|
} else { # insert the matches into the matchText widget |
514
|
|
|
|
|
|
|
foreach (@{$matches}) { |
515
|
|
|
|
|
|
|
$_ = $self->_parseTdListLine($_); # convert TDLIST line to player |
516
|
|
|
|
|
|
|
$m->insert('end', $self->_format($_) . "\n"); |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
$self->{active} = 1; |
519
|
|
|
|
|
|
|
$self->_moveListSelection(0); |
520
|
|
|
|
|
|
|
$self->{matchListValid} = 1; |
521
|
|
|
|
|
|
|
$self->_markMatchDups(); |
522
|
|
|
|
|
|
|
} |
523
|
|
|
|
|
|
|
} |
524
|
|
|
|
|
|
|
# Restore size: |
525
|
|
|
|
|
|
|
$m->GeometryRequest($width, $height); |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
sub _markMatchDups { |
529
|
|
|
|
|
|
|
my $self = shift; |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
my $m = $self->{matchText}; |
532
|
|
|
|
|
|
|
my @lines = ('dummy', split ("\n", $m->get("1.0", "end"))); # dummy line in front |
533
|
|
|
|
|
|
|
$self->_getDupKeys(); # make sure dup keys are up to date |
534
|
|
|
|
|
|
|
$m->tagDelete('dup'); # remove all previous duplicate tags |
535
|
|
|
|
|
|
|
for (my $ii = 1; $ii < @{$self->{matches}} + 1; $ii++) { |
536
|
|
|
|
|
|
|
my $p = $self->{matches}[$ii - 1]; |
537
|
|
|
|
|
|
|
my $pid = lc("$p->{country}$p->{agaNum}"); |
538
|
|
|
|
|
|
|
my $name = lc($p->{name}); # lower case name to create key |
539
|
|
|
|
|
|
|
$name =~ s/\s//g; # and remove all whitespace |
540
|
|
|
|
|
|
|
$m->tagAdd('dup', "$ii.0", "$ii.8") |
541
|
|
|
|
|
|
|
if (exists($self->{pids}{$pid})); |
542
|
|
|
|
|
|
|
$m->tagAdd('dup', "$ii.9", "$ii.9 + " . $self->cget('-namelength') . " chars") |
543
|
|
|
|
|
|
|
if (exists($self->{names}{$name})); |
544
|
|
|
|
|
|
|
} |
545
|
|
|
|
|
|
|
$m->tagConfigure('dup', |
546
|
|
|
|
|
|
|
-foreground => 'red'); |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
sub _search { |
550
|
|
|
|
|
|
|
my ($self, $srchString) = @_; |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
my @keys = (split '\s+', $srchString); |
553
|
|
|
|
|
|
|
return () unless(@keys); |
554
|
|
|
|
|
|
|
my @filtered = @tdList; |
555
|
|
|
|
|
|
|
while (@keys) { |
556
|
|
|
|
|
|
|
my $re = shift(@keys); |
557
|
|
|
|
|
|
|
if ($self->{tdEntry}->case()) { |
558
|
|
|
|
|
|
|
eval { @filtered = grep(/$re/, @filtered) }; |
559
|
|
|
|
|
|
|
} else { |
560
|
|
|
|
|
|
|
eval { @filtered = grep(/$re/i, @filtered) }; |
561
|
|
|
|
|
|
|
} |
562
|
|
|
|
|
|
|
if ($@) { |
563
|
|
|
|
|
|
|
return ('Illegal or incomplete regular expression:', $@); |
564
|
|
|
|
|
|
|
} |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
return \@filtered; |
567
|
|
|
|
|
|
|
} |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
# format a playerRef into register.tde format |
570
|
|
|
|
|
|
|
sub _format { |
571
|
|
|
|
|
|
|
my ($self, $p) = @_; |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
$p->{name} =~ s/\s+/ /g; # turn all multiple whitespace into single space |
574
|
|
|
|
|
|
|
$p->{name} =~ s/ ,/,/g; # no space in front of comma |
575
|
|
|
|
|
|
|
if (length($p->{name}) > $self->cget('-namelength')) { |
576
|
|
|
|
|
|
|
$self->configure(-namelength => length($p->{name})); |
577
|
|
|
|
|
|
|
$self->{lengthChange} = 1; |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
$p->{club} =~ s/^club=\s*//i; |
580
|
|
|
|
|
|
|
if ($p->{club} eq '') { |
581
|
|
|
|
|
|
|
if ($p->{name} =~ m/(.*?),/) { |
582
|
|
|
|
|
|
|
# use last name as club (reduce inter-family pairings) |
583
|
|
|
|
|
|
|
$p->{club} = $1; |
584
|
|
|
|
|
|
|
$p->{club} =~ s/\W//g; # remove all non-word chars |
585
|
|
|
|
|
|
|
} |
586
|
|
|
|
|
|
|
} |
587
|
|
|
|
|
|
|
if (length($p->{club}) > $self->cget('-clublength')) { |
588
|
|
|
|
|
|
|
$self->configure(-clublength => length($p->{club})); |
589
|
|
|
|
|
|
|
$self->{lengthChange} = 1; |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
unless ($p->{club} eq '') { |
592
|
|
|
|
|
|
|
$p->{club} = "CLUB=$p->{club}" |
593
|
|
|
|
|
|
|
} |
594
|
|
|
|
|
|
|
unless (exists($p->{country})) { |
595
|
|
|
|
|
|
|
$p->{country} = 'TMP'; |
596
|
|
|
|
|
|
|
} |
597
|
|
|
|
|
|
|
my $pid = "$p->{country}$p->{agaNum}"; |
598
|
|
|
|
|
|
|
unless(exists($self->{ratingChanged}{$pid})) { |
599
|
|
|
|
|
|
|
$self->{ratingOrg}{$pid} = |
600
|
|
|
|
|
|
|
$self->{ratingChanged}{$pid} = int($self->{agaTourn}->RankToRating($p->{agaRating})); |
601
|
|
|
|
|
|
|
} |
602
|
|
|
|
|
|
|
my $r; |
603
|
|
|
|
|
|
|
if ($self->{ratingOrg}{$pid} == $self->{ratingChanged}{$pid}) { |
604
|
|
|
|
|
|
|
# original - use rating or a rank? |
605
|
|
|
|
|
|
|
if ((defined($p->{agaRank}) or # always exists, but is undefined if rating is valid |
606
|
|
|
|
|
|
|
(lc($p->{country}) eq 'tmp'))) { # TMPs always use low accuraccy D/K style |
607
|
|
|
|
|
|
|
if (defined($p->{agaRank})) { |
608
|
|
|
|
|
|
|
$r = uc($p->{agaRank}); |
609
|
|
|
|
|
|
|
} else { |
610
|
|
|
|
|
|
|
$r = uc(_ratingToRank($p->{agaRating})); |
611
|
|
|
|
|
|
|
} |
612
|
|
|
|
|
|
|
} else { |
613
|
|
|
|
|
|
|
$r = $p->{agaRating}; |
614
|
|
|
|
|
|
|
} |
615
|
|
|
|
|
|
|
} else { |
616
|
|
|
|
|
|
|
$r = _ratingToRank($self->{ratingChanged}{$pid}); # changes are always a rank |
617
|
|
|
|
|
|
|
} |
618
|
|
|
|
|
|
|
return sprintf("$p->{country}%05d %-*s %5s %-*s $p->{flags} # $p->{comment}", |
619
|
|
|
|
|
|
|
$p->{agaNum}, $self->cget('-namelength'), $p->{name}, $r, |
620
|
|
|
|
|
|
|
$self->cget('-clublength'), $p->{club}); |
621
|
|
|
|
|
|
|
} |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
sub _ratingToRank { |
624
|
|
|
|
|
|
|
my ($rating) = @_; |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
return sprintf(" %2d%s", ($rating > 0) ? $rating : -$rating, ($rating > 0) ? 'D' : 'K'); |
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
###################################################### |
630
|
|
|
|
|
|
|
# |
631
|
|
|
|
|
|
|
# Public methods |
632
|
|
|
|
|
|
|
# |
633
|
|
|
|
|
|
|
##################################################### |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
=head1 METHODS |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
=over 4 |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
=item $tdFinder->B() |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
Clears the entire TDFinder, including the tdeText, matchText, and tdEntry |
642
|
|
|
|
|
|
|
subwidgets. |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
=cut |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
sub clear { |
647
|
|
|
|
|
|
|
my ($self) = @_; |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
$self->{matchListValid} = 0; # doesn't contain valid TDE entries |
650
|
|
|
|
|
|
|
$self->{ratingOrg} = {}; |
651
|
|
|
|
|
|
|
$self->{ratingChanged} = {}; |
652
|
|
|
|
|
|
|
$self->{tmpNum} = 1; |
653
|
|
|
|
|
|
|
$self->{tdeText}->delete('1.0', 'end'); |
654
|
|
|
|
|
|
|
$self->{matchText}->delete('1.0', 'end'); |
655
|
|
|
|
|
|
|
$self->_clearListSelection; # clear entry widget |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
=item $tdeFinder->B($player) |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
Adds a player to the TDFinder. Player should be a reference to a hash |
661
|
|
|
|
|
|
|
containing the following members: |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
$p->{agaNum} required |
664
|
|
|
|
|
|
|
$p->{country} required |
665
|
|
|
|
|
|
|
$p->{name} required |
666
|
|
|
|
|
|
|
$p->{agaRating} required |
667
|
|
|
|
|
|
|
$p->{club} optional |
668
|
|
|
|
|
|
|
$p->{flags} optional |
669
|
|
|
|
|
|
|
$p->{comment} optional |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
=cut |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
sub addPlayer { |
674
|
|
|
|
|
|
|
my ($self, $p) = @_; |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
my $t = $self->{tdeText}; |
677
|
|
|
|
|
|
|
$t->tagConfigure($self->{mostRecentInsert}, |
678
|
|
|
|
|
|
|
-foreground => $self->{tdeForeground}); # back to normal |
679
|
|
|
|
|
|
|
return unless (defined $p); # so we can un-mark by adding undef |
680
|
|
|
|
|
|
|
foreach (qw(agaNum country name agaRating)) { |
681
|
|
|
|
|
|
|
next if(defined($p->{$_})); |
682
|
|
|
|
|
|
|
carp ("No $_ defined for player\n"); |
683
|
|
|
|
|
|
|
return; |
684
|
|
|
|
|
|
|
} |
685
|
|
|
|
|
|
|
foreach (qw(club flags comment)) { |
686
|
|
|
|
|
|
|
$p->{$_} = '' unless defined($p->{$_}); |
687
|
|
|
|
|
|
|
} |
688
|
|
|
|
|
|
|
my $player = $self->_format($p); |
689
|
|
|
|
|
|
|
$player =~ m/^\s*(\S*)/; |
690
|
|
|
|
|
|
|
my $tag = $self->{mostRecentInsert} = lc($1); # save most recent insertion |
691
|
|
|
|
|
|
|
# print "insert player(tag=$tag) at end: $player\n"; |
692
|
|
|
|
|
|
|
$t->insert('end', "$player\n", $tag); |
693
|
|
|
|
|
|
|
$t->tagConfigure($tag, |
694
|
|
|
|
|
|
|
-foreground => 'darkgreen',); |
695
|
|
|
|
|
|
|
$t->see('end'); |
696
|
|
|
|
|
|
|
$self->_markTdeDups(); |
697
|
|
|
|
|
|
|
$self->eventGenerate('<>'); |
698
|
|
|
|
|
|
|
} |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
=item $tdFinder->B('line in TDLIST format') |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
Parses a line from the TDLIST file and adds the player to tdeText. |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
=cut |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
sub addTD { |
707
|
|
|
|
|
|
|
my ($self, $td) = @_; |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
$self->addPlayer($self->_parseTdListLine($td)); |
710
|
|
|
|
|
|
|
} |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
=item $tdFinder->B('line in register.tde format') |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
Parses a line from the register.tde file and adds the player to tdeText. |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
=cut |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
sub addTDE { |
719
|
|
|
|
|
|
|
my ($self, $tde) = @_; |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
my $p = $self->_parseRegisterLine($tde); |
722
|
|
|
|
|
|
|
$self->addPlayer($p); |
723
|
|
|
|
|
|
|
if ((lc ($p->{country}) eq 'tmp') and |
724
|
|
|
|
|
|
|
($p->{agaNum} >= $self->{tmpNum})) { |
725
|
|
|
|
|
|
|
$self->{tmpNum} = $p->{agaNum}; |
726
|
|
|
|
|
|
|
} |
727
|
|
|
|
|
|
|
} |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
=item $tdFinder->B() |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
Sorts the entries in tdeText. Currently, only sorting by rank (strongest |
732
|
|
|
|
|
|
|
first) is supported. Comments lines are skipped over. |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
=cut |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
sub sort { |
738
|
|
|
|
|
|
|
my $self = shift; |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
my $t = $self->{tdeText}; |
741
|
|
|
|
|
|
|
my $ii; |
742
|
|
|
|
|
|
|
my @lines = ('dummy', split ("\n", $t->get("1.0", "end"))); # dummy line in front |
743
|
|
|
|
|
|
|
my @players; |
744
|
|
|
|
|
|
|
for ($ii = 1; $ii < @lines; $ii++) { |
745
|
|
|
|
|
|
|
$lines[$ii] =~ s/^\s*#.*//s; # filter out comment only lines |
746
|
|
|
|
|
|
|
next if ($lines[$ii] eq ''); |
747
|
|
|
|
|
|
|
push(@players, $self->{agaTourn}->ParseRegisterLine($lines[$ii])); |
748
|
|
|
|
|
|
|
} |
749
|
|
|
|
|
|
|
my @sortedPlayers = sort(_rankCompare @players); |
750
|
|
|
|
|
|
|
for ($ii = 1; $ii < @lines; $ii++) { |
751
|
|
|
|
|
|
|
next if ($lines[$ii] eq ''); |
752
|
|
|
|
|
|
|
my $player = $self->_format(shift(@sortedPlayers)); |
753
|
|
|
|
|
|
|
next if ($t->get("$ii.0", "$ii.0 lineend") eq $player); |
754
|
|
|
|
|
|
|
# print "delete line: $ii\n"; |
755
|
|
|
|
|
|
|
$t->delete("$ii.0", "$ii.0 lineend"); |
756
|
|
|
|
|
|
|
$player =~ m/^\s*(\S*)/; |
757
|
|
|
|
|
|
|
my $tag = lc($1); |
758
|
|
|
|
|
|
|
# print "insert player(tag=$tag) at line $ii: $player\n"; |
759
|
|
|
|
|
|
|
$t->insert("$ii.0", $player, $tag); # tag with AGA number (lower cased) |
760
|
|
|
|
|
|
|
} |
761
|
|
|
|
|
|
|
if (@sortedPlayers) { |
762
|
|
|
|
|
|
|
$self->Error('Players left over after sorting'); |
763
|
|
|
|
|
|
|
while (@sortedPlayers) { |
764
|
|
|
|
|
|
|
my $player = $self->_format(shift(@sortedPlayers)); |
765
|
|
|
|
|
|
|
# print "insert leftover player at end: $player\n"; |
766
|
|
|
|
|
|
|
$t->insert('end', $player, 'error'); |
767
|
|
|
|
|
|
|
} |
768
|
|
|
|
|
|
|
$t->tagConfigure('error', |
769
|
|
|
|
|
|
|
-foreground => 'red', |
770
|
|
|
|
|
|
|
-underline => 'true'); |
771
|
|
|
|
|
|
|
} |
772
|
|
|
|
|
|
|
$self->_markTdeDups(); |
773
|
|
|
|
|
|
|
$self->eventGenerate('<>'); |
774
|
|
|
|
|
|
|
} |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
1; |
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
__END__ |