ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC.pm
(Generate patch)

Comparing deliantra/Deliantra-Client/DC.pm (file contents):
Revision 1.76 by root, Mon May 29 21:20:15 2006 UTC vs.
Revision 1.124 by root, Wed Oct 11 23:34:24 2006 UTC

1=head1 NAME 1=head1 NAME
2 2
3CFClient - undocumented utility garbage for our crossfire client 3CFPlus - undocumented utility garbage for our crossfire client
4 4
5=head1 SYNOPSIS 5=head1 SYNOPSIS
6 6
7 use CFClient; 7 use CFPlus;
8 8
9=head1 DESCRIPTION 9=head1 DESCRIPTION
10 10
11=over 4 11=over 4
12 12
13=cut 13=cut
14 14
15package CFClient; 15package CFPlus;
16
17use Carp ();
16 18
17BEGIN { 19BEGIN {
18 $VERSION = '0.1'; 20 $VERSION = '0.52';
19 21
20 use XSLoader; 22 use XSLoader;
21 XSLoader::load "CFClient", $VERSION; 23 XSLoader::load "CFPlus", $VERSION;
24}
25
26BEGIN {
27 $SIG{__DIE__} = sub {
28 return if CFPlus::in_destruct;
29 #CFPlus::fatal $_[0];#d#
30 CFPlus::error Carp::longmess $_[0];#d#
31 die;#d#
32 };
22} 33}
23 34
24use utf8; 35use utf8;
25 36
26use Carp ();
27use AnyEvent (); 37use AnyEvent ();
28use BerkeleyDB; 38use BerkeleyDB;
39use Pod::POM ();
40use Scalar::Util ();
41use Storable (); # finally
42
43=item guard { BLOCK }
44
45Returns an object that executes the given block as soon as it is destroyed.
46
47=cut
48
49sub guard(&) {
50 bless \(my $cb = $_[0]), "CFPlus::Guard"
51}
52
53sub CFPlus::Guard::DESTROY {
54 ${$_[0]}->()
55}
56
57sub asxml($) {
58 local $_ = $_[0];
59
60 s/&/&/g;
61 s/>/>/g;
62 s/</&lt;/g;
63
64 $_
65}
66
67sub socketpipe() {
68 socketpair my $fh1, my $fh2, Socket::AF_UNIX, Socket::SOCK_STREAM, Socket::PF_UNSPEC
69 or die "cannot establish bidiretcional pipe: $!\n";
70
71 ($fh1, $fh2)
72}
73
74sub background(&) {
75 my ($cb) = @_;
76
77 my ($fh_r, $fh_w) = CFPlus::socketpipe;
78
79 my $pid = fork;
80
81 if (defined $pid && !$pid) {
82 local $SIG{__DIE__};
83
84 open STDOUT, ">&", $fh_w;
85 open STDERR, ">&", $fh_w;
86 close $fh_r;
87 close $fh_w;
88
89 $| = 1;
90
91 eval { $cb->() };
92
93 if ($@) {
94 my $msg = $@;
95 $msg =~ s/\n+/\n/;
96 warn "FATAL: $msg";
97 CFPlus::_exit 1;
98 }
99
100 # win32 is fucked up, of course. exit will clean stuff up,
101 # which destroys our database etc. _exit will exit ALL
102 # forked processes, because of the dreaded fork emulation.
103 CFPlus::_exit 0;
104 }
105
106 close $fh_w;
107
108 my $buffer;
109
110 Event->io (fd => $fh_r, poll => 'r', cb => sub {
111 unless (sysread $fh_r, $buffer, 4096, length $buffer) {
112 $_[0]->w->cancel;
113 $buffer .= "done\n";
114 }
115
116 while ($buffer =~ s/^(.*)\n//) {
117 my $line = $1;
118 $line =~ s/\s+$//;
119 utf8::decode $line;
120 ::message ({
121 markup => "editor($pid): " . CFPlus::asxml $line,
122 });
123 }
124 });
125}
126
127package CFPlus::Database;
128
129our @ISA = BerkeleyDB::Btree::;
130
131sub get($$) {
132 my $data;
133
134 $_[0]->db_get ($_[1], $data) == 0
135 ? $data
136 : ()
137}
138
139my %DB_SYNC;
140
141sub put($$$) {
142 my ($db, $key, $data) = @_;
143
144 my $hkey = $db + 0;
145 Scalar::Util::weaken $db;
146 $DB_SYNC{$hkey} ||= AnyEvent->timer (after => 5, cb => sub {
147 delete $DB_SYNC{$hkey};
148 $db->db_sync if $db;
149 });
150
151 $db->db_put ($key => $data)
152}
153
154package CFPlus;
29 155
30sub find_rcfile($) { 156sub find_rcfile($) {
31 my $path; 157 my $path;
32 158
33 for (grep !ref, @INC) { 159 for (grep !ref, @INC) {
34 $path = "$_/CFClient/resources/$_[0]"; 160 $path = "$_/CFPlus/resources/$_[0]";
35 return $path if -r $path; 161 return $path if -r $path;
36 } 162 }
37 163
38 die "FATAL: can't find required file $_[0]\n"; 164 die "FATAL: can't find required file $_[0]\n";
39} 165}
40 166
167BEGIN {
168 use Crossfire::Protocol::Base ();
169 *to_json = \&Crossfire::Protocol::Base::to_json;
170 *from_json = \&Crossfire::Protocol::Base::from_json;
171}
172
41sub read_cfg { 173sub read_cfg {
42 my ($file) = @_; 174 my ($file) = @_;
43 175
44 open CFG, $file 176 open my $fh, $file
45 or return; 177 or return;
46 178
47 my $CFG;
48
49 local $/; 179 local $/;
50 $CFG = eval <CFG>; 180 my $CFG = <$fh>;
51 181
52 $::CFG = $CFG; 182 if ($CFG =~ /^---/) { ## TODO compatibility cruft, remove
53 183 require YAML;
54 close CFG; 184 utf8::decode $CFG;
185 $::CFG = YAML::Load ($CFG);
186 } elsif ($CFG =~ /^\{/) {
187 $::CFG = from_json $CFG;
188 } else {
189 $::CFG = eval $CFG; ## todo comaptibility cruft
190 }
55} 191}
56 192
57sub write_cfg { 193sub write_cfg {
58 my ($file) = @_; 194 my ($file) = @_;
59 195
60 open CFG, ">$file" 196 $::CFG->{VERSION} = $::VERSION;
197
198 open my $fh, ">:utf8", $file
61 or return; 199 or return;
200 print $fh to_json $::CFG;
201}
62 202
63 { 203sub http_proxy {
64 require Data::Dumper; 204 my @proxy = win32_proxy_info;
65 local $Data::Dumper::Purity = 1; 205
66 $::CFG->{VERSION} = $::VERSION; 206 if (@proxy) {
67 print CFG Data::Dumper->Dump ([$::CFG], [qw/CFG/]); 207 "http://" . (@proxy < 2 ? "" : @proxy < 3 ? "$proxy[1]\@" : "$proxy[1]:$proxy[2]\@") . $proxy[0]
208 } elsif (exists $ENV{http_proxy}) {
209 $ENV{http_proxy}
210 } else {
211 ()
68 } 212 }
69
70 close CFG;
71} 213}
72 214
73mkdir "$Crossfire::VARDIR/cfplus", 0777; 215sub set_proxy {
216 my $proxy = http_proxy
217 or return;
74 218
75{ 219 $ENV{http_proxy} = $proxy;
76 use strict;
77
78 our $DB_ENV = new BerkeleyDB::Env
79 -Home => "$Crossfire::VARDIR/cfplus",
80 -Cachesize => 1_000_000,
81 -ErrFile => "$Crossfire::VARDIR/cfplus/errorlog.txt",
82# -ErrPrefix => "DATABASE",
83 -Verbose => 1,
84 -Flags => DB_CREATE | DB_RECOVER | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN,
85 -SetFlags => DB_AUTO_COMMIT | DB_LOG_AUTOREMOVE | DB_TXN_WRITE_NOSYNC,
86 or die "unable to create/open database home $Crossfire::VARDIR/cfplus: $BerkeleyDB::Error";
87} 220}
221
222our $DB_ENV;
223our $DB_STATE;
88 224
89sub db_table($) { 225sub db_table($) {
90 my ($table) = @_; 226 my ($table) = @_;
91 227
92 $table =~ s/([^a-zA-Z0-9_\-])/sprintf "=%x=", ord $1/ge; 228 $table =~ s/([^a-zA-Z0-9_\-])/sprintf "=%x=", ord $1/ge;
93 229
94 new CFClient::Database 230 new CFPlus::Database
95 -Env => $DB_ENV, 231 -Env => $DB_ENV,
96 -Filename => $table, 232 -Filename => $table,
97# -Filename => "database", 233# -Filename => "database",
98# -Subname => $table, 234# -Subname => $table,
99 -Property => DB_CHKSUM, 235 -Property => DB_CHKSUM,
100 -Flags => DB_CREATE | DB_UPGRADE, 236 -Flags => DB_CREATE | DB_UPGRADE,
101 or die "unable to create/open database table $_[0]: $BerkeleyDB::Error" 237 or die "unable to create/open database table $_[0]: $BerkeleyDB::Error"
102} 238}
103 239
104sub pod_to_pango($) { 240{
105 my ($pom) = @_; 241 use strict;
106 242
107 $pom->present ("CFClient::PodToPango") 243 mkdir "$Crossfire::VARDIR/cfplus", 0777;
108} 244 my $recover = $BerkeleyDB::db_version >= 4.4
245 ? eval "DB_REGISTER | DB_RECOVER"
246 : 0;
109 247
110sub pod_to_pango_list($) { 248 $DB_ENV = new BerkeleyDB::Env
111 my ($pom) = @_; 249 -Home => "$Crossfire::VARDIR/cfplus",
250 -Cachesize => 1_000_000,
251 -ErrFile => "$Crossfire::VARDIR/cfplus/errorlog.txt",
252# -ErrPrefix => "DATABASE",
253 -Verbose => 1,
254 -Flags => DB_CREATE | DB_RECOVER | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN | $recover,
255 -SetFlags => DB_AUTO_COMMIT | DB_LOG_AUTOREMOVE,
256 or die "unable to create/open database home $Crossfire::VARDIR/cfplus: $BerkeleyDB::Error";
112 257
113 [ 258 $DB_STATE = db_table "state";
114 map s/^(\s*)// && [40 * length $1, length $_ ? $_ : " "],
115 split /\n/, $pom->present ("CFClient::PodToPango")
116 ]
117} 259}
118 260
119package CFClient::PodToPango; 261package CFPlus::Layout;
120 262
121use base Pod::POM::View::Text; 263$CFPlus::OpenGL::SHUTDOWN_HOOK{"CFPlus::Layout"} = sub {
122 264 reset_glyph_cache;
123our $indent = 0;
124
125*view_seq_code =
126*view_seq_bold = sub { "<b>$_[1]</b>" };
127*view_seq_italic = sub { "<i>$_[1]</i>" };
128*view_seq_space =
129*view_seq_link =
130*view_seq_index = sub { CFClient::UI::Label::escape ($_[1]) };
131
132sub view_seq_text {
133 my $text = $_[1];
134 $text =~ s/\s+/ /g;
135 CFClient::UI::Label::escape ($text)
136}
137
138sub view_item {
139 ("\t" x ($indent / 4))
140 . $_[1]->title->present ($_[0])
141 . "\n"
142 . $_[1]->content->present ($_[0])
143}
144
145sub view_verbatim {
146 (join "",
147 map +("\t" x ($indent / 2)) . "<tt>$_</tt>\n",
148 split /\n/, CFClient::UI::Label::escape ($_[1]))
149 . "\n"
150}
151
152sub view_textblock {
153 ("\t" x ($indent / 2)) . "$_[1]\n\n"
154}
155
156sub view_head1 {
157 "\n\n<span foreground='#ffff00' size='x-large'>" . $_[1]->title->present ($_[0]) . "</span>\n\n"
158 . $_[1]->content->present ($_[0])
159}; 265};
160 266
161sub view_head2 {
162 "\n<span foreground='#ccccff' size='large'>" . $_[1]->title->present ($_[0]) . "</span>\n\n"
163 . $_[1]->content->present ($_[0])
164};
165
166sub view_head3 {
167 "\n<span size='large'>" . $_[1]->title->present ($_[0]) . "</span>\n\n"
168 . $_[1]->content->present ($_[0])
169};
170
171sub view_over {
172 local $indent = $indent + $_[1]->indent;
173 $_[1]->content->present ($_[0])
174}
175
176package CFClient::Database;
177
178our @ISA = BerkeleyDB::Btree::;
179
180sub get($$) {
181 my $data;
182
183 $_[0]->db_get ($_[1], $data) == 0
184 ? $data
185 : ()
186}
187
188my %DB_SYNC;
189
190sub put($$$) {
191 my ($db, $key, $data) = @_;
192
193 $DB_SYNC{$db} = AnyEvent->timer (after => 5, cb => sub { $db->db_sync });
194
195 $db->db_put ($key => $data)
196}
197
198package CFClient::Item; 267package CFPlus::Item;
199 268
200use strict; 269use strict;
201use Crossfire::Protocol::Constants; 270use Crossfire::Protocol::Constants;
271
272my $last_enter_count = 1;
202 273
203sub desc_string { 274sub desc_string {
204 my ($self) = @_; 275 my ($self) = @_;
205 276
206 my $desc = 277 my $desc =
232 my $weight = ($self->{nrof} || 1) * $self->{weight}; 303 my $weight = ($self->{nrof} || 1) * $self->{weight};
233 304
234 $weight < 0 ? "?" : $weight * 0.001 305 $weight < 0 ? "?" : $weight * 0.001
235} 306}
236 307
308sub do_n_dialog {
309 my ($cb) = @_;
310
311 my $w = new CFPlus::UI::Toplevel
312 on_delete => sub { $_[0]->destroy; 1 },
313 has_close_button => 1,
314 ;
315
316 $w->add (my $vb = new CFPlus::UI::VBox x => "center", y => "center");
317 $vb->add (new CFPlus::UI::Label text => "Enter item count:");
318 $vb->add (my $entry = new CFPlus::UI::Entry
319 text => $last_enter_count,
320 on_activate => sub {
321 my ($entry) = @_;
322 $last_enter_count = $entry->get_text;
323 $cb->($last_enter_count);
324 $w->hide;
325 $w->destroy;
326
327 0
328 },
329 on_escape => sub { $w->destroy; 1 },
330 );
331 $entry->grab_focus;
332 $w->show;
333}
334
237sub update_widgets { 335sub update_widgets {
238 my ($self) = @_; 336 my ($self) = @_;
239 337
338 # necessary to avoid cyclic references
339 Scalar::Util::weaken $self;
340
240 my $button_cb = sub { 341 my $button_cb = sub {
241 my (undef, $ev, $x, $y) = @_; 342 my (undef, $ev, $x, $y) = @_;
242 343
243 if (($ev->{mod} & CFClient::KMOD_SHIFT) && $ev->{button} == 1) {
244 my $targ = $::CONN->{player}{tag}; 344 my $targ = $::CONN->{player}{tag};
245 345
246 if ($self->{container} == $::CONN->{player}{tag}) { 346 if ($self->{container} == $::CONN->{player}{tag}) {
247 $targ = $::CONN->{open_container}; 347 $targ = $::CONN->{open_container};
248 } 348 }
249 349
350 if (($ev->{mod} & CFPlus::KMOD_SHIFT) && $ev->{button} == 1) {
250 $::CONN->send ("move $targ $self->{tag} 0"); 351 $::CONN->send ("move $targ $self->{tag} 0")
352 if $targ || !($self->{flags} & F_LOCKED);
353 } elsif (($ev->{mod} & CFPlus::KMOD_SHIFT) && $ev->{button} == 2) {
354 $self->{flags} & F_LOCKED
355 ? $::CONN->send ("lock " . pack "CN", 0, $self->{tag})
356 : $::CONN->send ("lock " . pack "CN", 1, $self->{tag})
251 } elsif ($ev->{button} == 1) { 357 } elsif ($ev->{button} == 1) {
252 $::CONN->send ("examine $self->{tag}"); 358 $::CONN->send ("examine $self->{tag}");
253 } elsif ($ev->{button} == 2) { 359 } elsif ($ev->{button} == 2) {
254 $::CONN->send ("apply $self->{tag}"); 360 $::CONN->send ("apply $self->{tag}");
255 } elsif ($ev->{button} == 3) { 361 } elsif ($ev->{button} == 3) {
362 my $move_prefix = $::CONN->{open_container} ? 'put' : 'drop';
363 if ($self->{container} == $::CONN->{open_container}) {
364 $move_prefix = "take";
365 }
366
256 my @menu_items = ( 367 my @menu_items = (
257 ["examine", sub { $::CONN->send ("examine $self->{tag}") }], 368 ["examine", sub { $::CONN->send ("examine $self->{tag}") }],
258 ["mark", sub { $::CONN->send ("mark ". pack "N", $self->{tag}) }], 369 ["mark", sub { $::CONN->send ("mark ". pack "N", $self->{tag}) }],
370 ["ignite/thaw", # first try of an easier use of flint&steel
371 sub {
372 $::CONN->send ("mark ". pack "N", $self->{tag});
373 $::CONN->send ("command apply flint and steel");
374 }
375 ],
376 ["inscribe", # first try of an easier use of flint&steel
377 sub {
378 &::open_string_query ("Text to inscribe", sub {
379 my ($entry, $txt) = @_;
380 $::CONN->send ("mark ". pack "N", $self->{tag});
381 $::CONN->send ("command use_skill inscription $txt");
382 });
383 }
384 ],
385 ["rename", # first try of an easier use of flint&steel
386 sub {
387 &::open_string_query ("Rename item to:", sub {
388 my ($entry, $txt) = @_;
389 $::CONN->send ("mark ". pack "N", $self->{tag});
390 $::CONN->send ("command rename to <$txt>");
391 }, $self->{name},
392 "If you input no name or erase the current custom name, the custom name will be unset");
393 }
394 ],
259 ["apply", sub { $::CONN->send ("apply $self->{tag}") }], 395 ["apply", sub { $::CONN->send ("apply $self->{tag}") }],
260 ( 396 (
261 $self->{flags} & F_LOCKED 397 $self->{flags} & F_LOCKED
262 ? ( 398 ? (
263 ["unlock", sub { $::CONN->send ("lock " . pack "CN", 0, $self->{tag}) }], 399 ["unlock", sub { $::CONN->send ("lock " . pack "CN", 0, $self->{tag}) }],
264 ) 400 )
265 : ( 401 : (
266 ["lock", sub { $::CONN->send ("lock " . pack "CN", 1, $self->{tag}) }], 402 ["lock", sub { $::CONN->send ("lock " . pack "CN", 1, $self->{tag}) }],
267 ["drop", sub { $::CONN->send ("move $::CONN->{open_container} $self->{tag} 0") }], 403 ["$move_prefix all", sub { $::CONN->send ("move $targ $self->{tag} 0") }],
404 ["$move_prefix &lt;n&gt;",
405 sub {
406 do_n_dialog (sub { $::CONN->send ("move $targ $self->{tag} $_[0]") })
407 }
408 ]
268 ) 409 )
269 ), 410 ),
270 ); 411 );
271 412
272 CFClient::UI::Menu->new (items => \@menu_items)->popup ($ev); 413 CFPlus::UI::Menu->new (items => \@menu_items)->popup ($ev);
273 } 414 }
274 415
275 1 416 1
276 }; 417 };
277 418
278 my $tooltip_std = "<small>" 419 my $tooltip_std = "<small>"
279 . "Left click - examine item\n" 420 . "Left click - examine item\n"
280 . "Shift-Left click - " . ($self->{container} ? "move or drop" : "take") . " item\n" 421 . "Shift-Left click - " . ($self->{container} ? "move or drop" : "take") . " item\n"
281 . "Middle click - apply\n" 422 . "Middle click - apply\n"
423 . "Shift-Middle click - lock/unlock\n"
282 . "Right click - further options" 424 . "Right click - further options"
283 . "</small>\n"; 425 . "</small>\n";
284 426
427 my $bg = $self->{flags} & F_CURSED ? [1 , 0 , 0, 0.5]
428 : $self->{flags} & F_MAGIC ? [0.2, 0.2, 1, 0.5]
429 : undef;
430
285 $self->{face_widget} ||= new CFClient::UI::Face 431 $self->{face_widget} ||= new CFPlus::UI::Face
286 can_events => 1, 432 can_events => 1,
287 can_hover => 1, 433 can_hover => 1,
288 anim => $self->{anim}, 434 anim => $self->{anim},
289 animspeed => $self->{animspeed}, # TODO# must be set at creation time 435 animspeed => $self->{animspeed}, # TODO# must be set at creation time
290 on_button_down => $button_cb, 436 on_button_down => $button_cb,
291 ; 437 ;
438 $self->{face_widget}{bg} = $bg;
292 $self->{face_widget}{face} = $self->{face}; 439 $self->{face_widget}{face} = $self->{face};
293 $self->{face_widget}{anim} = $self->{anim}; 440 $self->{face_widget}{anim} = $self->{anim};
294 $self->{face_widget}{animspeed} = $self->{animspeed}; 441 $self->{face_widget}{animspeed} = $self->{animspeed};
295 $self->{face_widget}->set_tooltip ( 442 $self->{face_widget}->set_tooltip (
296 "<b>Face/Animation.</b>\n" 443 "<b>Face/Animation.</b>\n"
297 . "Item uses face #$self->{face}. " 444 . "Item uses face #$self->{face}. "
298 . ($self->{animspeed} ? "Item uses animation #$self->{anim} at " . (1 / $self->{animspeed}) . "fps. " : "Item is not animated. ") 445 . ($self->{animspeed} ? "Item uses animation #$self->{anim} at " . (1 / $self->{animspeed}) . "fps. " : "Item is not animated. ")
299 . "\n\n$tooltip_std" 446 . "\n\n$tooltip_std"
300 ); 447 );
301 448
302 $self->{desc_widget} ||= new CFClient::UI::Label 449 $self->{desc_widget} ||= new CFPlus::UI::Label
303 can_events => 1, 450 can_events => 1,
304 can_hover => 1, 451 can_hover => 1,
305 ellipsise => 2, 452 ellipsise => 2,
306 align => -1, 453 align => -1,
307 on_button_down => $button_cb, 454 on_button_down => $button_cb,
308 ; 455 ;
309 my $desc = CFClient::Item::desc_string $self; 456 my $desc = CFPlus::Item::desc_string $self;
457 $self->{desc_widget}{bg} = $bg;
310 $self->{desc_widget}->set_text ($desc); 458 $self->{desc_widget}->set_text ($desc);
311 $self->{desc_widget}->set_tooltip ("<b>$desc</b>.\n$tooltip_std"); 459 $self->{desc_widget}->set_tooltip ("<b>$desc</b>.\n$tooltip_std");
312 460
313 $self->{weight_widget} ||= new CFClient::UI::Label 461 $self->{weight_widget} ||= new CFPlus::UI::Label
314 can_events => 1, 462 can_events => 1,
315 can_hover => 1, 463 can_hover => 1,
316 ellipsise => 0, 464 ellipsise => 0,
317 align => 0, 465 align => 0,
318 on_button_down => $button_cb, 466 on_button_down => $button_cb,
319 ; 467 ;
468 $self->{weight_widget}{bg} = $bg;
320 $self->{weight_widget}->set_text (CFClient::Item::weight_string $self); 469 $self->{weight_widget}->set_text (CFPlus::Item::weight_string $self);
321
322 $self->{weight_widget}->set_tooltip ( 470 $self->{weight_widget}->set_tooltip (
323 "<b>Weight</b>.\n" 471 "<b>Weight</b>.\n"
324 . ($self->{weight} >= 0 ? "One item weighs $self->{weight}g. " : "You have no idea how much this weighs. ") 472 . ($self->{weight} >= 0 ? "One item weighs $self->{weight}g. " : "You have no idea how much this weighs. ")
325 . ($self->{nrof} ? "You have $self->{nrof} of it. " : "Item cannot stack with others of it's kind. ") 473 . ($self->{nrof} ? "You have $self->{nrof} of it. " : "Item cannot stack with others of it's kind. ")
326 . "\n\n$tooltip_std" 474 . "\n\n$tooltip_std"
327 ); 475 );
328} 476}
329 477
330package CFClient::Recorder;
331
332our $RECORD_WINDOW;
333
334my $CMDBOX;
335my $CURRENT_CMDS;
336my $REC_BTN;
337
338my @ALLOWED_MODIFIER_KEYS = (
339 (CFClient::SDLK_LSHIFT) => "LSHIFT",
340 (CFClient::SDLK_LCTRL ) => "LCTRL",
341 (CFClient::SDLK_LALT ) => "LALT",
342 (CFClient::SDLK_LMETA ) => "LMETA",
343
344 (CFClient::SDLK_RSHIFT) => "RSHIFT",
345 (CFClient::SDLK_RCTRL ) => "RCTRL",
346 (CFClient::SDLK_RALT ) => "RALT",
347 (CFClient::SDLK_RMETA ) => "RMETA",
348);
349
350my %ALLOWED_MODIFIERS = (
351 (CFClient::KMOD_LSHIFT) => "LSHIFT",
352 (CFClient::KMOD_LCTRL ) => "LCTRL",
353 (CFClient::KMOD_LALT ) => "LALT",
354 (CFClient::KMOD_LMETA ) => "LMETA",
355
356 (CFClient::KMOD_RSHIFT) => "RSHIFT",
357 (CFClient::KMOD_RCTRL ) => "RCTRL",
358 (CFClient::KMOD_RALT ) => "RALT",
359 (CFClient::KMOD_RMETA ) => "RMETA",
360);
361
362my %DIRECT_BIND_CHARS = map { $_ => 1 } qw/0 1 2 3 4 5 6 7 8 9/;
363my @DIRECT_BIND_KEYS = (
364 CFClient::SDLK_F1,
365 CFClient::SDLK_F2,
366 CFClient::SDLK_F3,
367 CFClient::SDLK_F4,
368 CFClient::SDLK_F5,
369 CFClient::SDLK_F6,
370 CFClient::SDLK_F7,
371 CFClient::SDLK_F8,
372 CFClient::SDLK_F9,
373 CFClient::SDLK_F10,
374 CFClient::SDLK_F11,
375 CFClient::SDLK_F12,
376 CFClient::SDLK_F13,
377 CFClient::SDLK_F14,
378 CFClient::SDLK_F15,
379);
380
381# this binding dialog asks for a key-combo to be pressed
382# and if successful it binds the modifier+symbol to the
383# supplied actions in $cmd.
384# (Bindings are stored in $::CFG->{bindings}->{$mod}->{$sym})
385sub open_binding_dialog {
386 my ($cmd) = @_;
387
388 my $w = new CFClient::UI::FancyFrame
389 title => "Bind Action";
390
391 $w->add (my $vb = new CFClient::UI::VBox);
392 $vb->add (new CFClient::UI::Label
393 text => "Press a modifier (CTRL, ALT and/or SHIFT) and a key."
394 ."You can only bind 0-9 and F1-F15 without modifiers."
395 );
396 $vb->add (my $entry = new CFClient::UI::Entry
397 text => "",
398 on_key_down => sub {
399 my ($entry, $ev) = @_;
400
401 my $mod = $ev->{mod};
402 my $sym = $ev->{sym};
403
404 # XXX: This seems a little bit hackisch to me, but i have to ignore them
405 if (grep { $_ == $sym } @ALLOWED_MODIFIER_KEYS) {
406 return;
407 }
408
409 if ($mod == CFClient::KMOD_NONE
410 and not $DIRECT_BIND_CHARS{chr ($ev->{unicode})}
411 and not grep { $sym == $_ } @DIRECT_BIND_KEYS)
412 {
413 $::STATUSBOX->add (
414 "Can't bind key ".CFClient::SDL_GetKeyName ($sym)
415 ." directly without modifier! It would damage the completer handling."
416 );
417 return;
418 }
419
420 $entry->focus_out;
421
422 $::CFG->{bindings}->{$mod}->{$sym} = $cmd;
423 $::STATUSBOX->add ("Bound actions to '".keycombo_to_name ($mod, $sym)."'. Don't forget 'Save Config'!");
424
425 $w->destroy
426 });
427
428 $entry->focus_in;
429 $w->center;
430 $w->show;
431}
432
433sub keycombo_to_name {
434 my ($mod, $sym) = @_;
435
436 my $mods = join '+',
437 map { $ALLOWED_MODIFIERS{$_} }
438 grep { $_ & $mod }
439 keys %ALLOWED_MODIFIERS;
440 $mods .= "+" if $mods ne '';
441
442 return $mods . CFClient::SDL_GetKeyName ($sym);
443}
444
445sub clear_command_list {
446 $CMDBOX->clear () if $CMDBOX;
447}
448
449sub set_command_list {
450 my ($list) = @_;
451
452 return unless $CMDBOX;
453
454 $CMDBOX->clear ();
455 $CURRENT_CMDS = $list;
456
457 my $idx = 0;
458
459 for (@$list) {
460 $CMDBOX->add (my $hb = new CFClient::UI::HBox);
461
462 my $i = $idx;
463 $hb->add (new CFClient::UI::Button
464 text => "delete",
465 tooltip => "Deletes the action from the record",
466 on_activate => sub {
467 $CMDBOX->remove ($hb);
468 $list->[$i] = undef;
469 });
470
471 $hb->add (new CFClient::UI::Label text => $_);
472
473 $idx++
474 }
475}
476
477# if $show is 1 the recorder will be shown
478sub start {
479 my ($show) = @_;
480
481 $RECORD_WINDOW->show if $show;
482
483 $REC_BTN->set_text ("stop recording");
484 $REC_BTN->{recording} = 1;
485 clear_command_list;
486 $::CONN->start_record;
487}
488
489# if $autobind is 1 the recorder will be automatically
490# jump into the binding query and hide the recorder window
491sub stop {
492 my ($autobind) = @_;
493
494 $REC_BTN->set_text ("start recording");
495 $REC_BTN->{recording} = 0;
496
497 my $rec = $::CONN->stop_record;
498 return unless ref $rec eq 'ARRAY';
499 set_command_list ($rec);
500
501 if ($autobind) {
502 open_binding_dialog ([ grep { defined $_ } @$CURRENT_CMDS ]);
503 $RECORD_WINDOW->hide;
504 }
505}
506
507sub make_window {
508 $RECORD_WINDOW = new CFClient::UI::FancyFrame
509 req_y => 1,
510 req_x => -1,
511 title => "Action Recorder";
512
513 $RECORD_WINDOW->add (my $vb = new CFClient::UI::VBox);
514 $vb->add ($REC_BTN = new CFClient::UI::Button
515 text => "start recording",
516 tooltip => "Start/Stops recording of actions."
517 ."(CTRL+Insert Starts the recorder, Insert Stops recorder and binds automatically)"
518 ."All subsequent actions after the recording started will be captured."
519 ."The actions are displayed after the record was stopped."
520 ."To bind the action you have to click on the 'Bind' button",
521 on_activate => sub {
522 my ($btn) = @_;
523
524 unless ($btn->{recording}) {
525 start;
526 } else {
527 stop;
528 }
529 });
530 $vb->add ($CMDBOX = new CFClient::UI::VBox);
531 $vb->add (new CFClient::UI::Button
532 text => "bind",
533 tooltip => "This opens a query where you have to press the key combination to bind the recorded actions",
534 on_activate => sub {
535 open_binding_dialog ([ grep { defined $_ } @$CURRENT_CMDS ]);
536 });
537
538 $RECORD_WINDOW
539}
540
5411; 4781;
542 479
543=back 480=back
544 481
545=head1 AUTHOR 482=head1 AUTHOR

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines