ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC.pm
Revision: 1.93
Committed: Sun Jun 18 17:13:10 2006 UTC (17 years, 10 months ago) by root
Branch: MAIN
Changes since 1.92: +3 -3 lines
Log Message:
tinkering with events and close button

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3 root 1.22 CFClient - undocumented utility garbage for our crossfire client
4 root 1.1
5     =head1 SYNOPSIS
6    
7 root 1.22 use CFClient;
8 root 1.1
9     =head1 DESCRIPTION
10    
11     =over 4
12    
13     =cut
14    
15 root 1.22 package CFClient;
16 root 1.1
17     BEGIN {
18     $VERSION = '0.1';
19    
20 root 1.2 use XSLoader;
21 root 1.22 XSLoader::load "CFClient", $VERSION;
22 root 1.1 }
23    
24 root 1.62 use utf8;
25    
26 root 1.43 use Carp ();
27 root 1.52 use AnyEvent ();
28 root 1.34 use BerkeleyDB;
29 root 1.89 use Pod::POM ();
30 root 1.92 use Scalar::Util ();
31 root 1.89 use Storable (); # finally
32    
33     package CFClient::PodToPango;
34    
35     use base Pod::POM::View::Text;
36    
37     our $VERSION = 1; # bump if resultant formatting changes
38    
39     our $indent = 0;
40    
41     *view_seq_code =
42     *view_seq_bold = sub { "<b>$_[1]</b>" };
43     *view_seq_italic = sub { "<i>$_[1]</i>" };
44     *view_seq_space =
45     *view_seq_link =
46     *view_seq_index = sub { CFClient::UI::Label::escape ($_[1]) };
47    
48     sub view_seq_text {
49     my $text = $_[1];
50     $text =~ s/\s+/ /g;
51     CFClient::UI::Label::escape ($text)
52     }
53    
54     sub view_item {
55     ("\t" x ($indent / 4))
56     . $_[1]->title->present ($_[0])
57 root 1.91 . "\n\n"
58 root 1.89 . $_[1]->content->present ($_[0])
59     }
60    
61     sub view_verbatim {
62     (join "",
63     map +("\t" x ($indent / 2)) . "<tt>$_</tt>\n",
64     split /\n/, CFClient::UI::Label::escape ($_[1]))
65     . "\n"
66     }
67    
68     sub view_textblock {
69     ("\t" x ($indent / 2)) . "$_[1]\n\n"
70     }
71    
72     sub view_head1 {
73     "\n\n<span foreground='#ffff00' size='x-large'>" . $_[1]->title->present ($_[0]) . "</span>\n\n"
74     . $_[1]->content->present ($_[0])
75     };
76    
77     sub view_head2 {
78     "\n<span foreground='#ccccff' size='large'>" . $_[1]->title->present ($_[0]) . "</span>\n\n"
79     . $_[1]->content->present ($_[0])
80     };
81    
82     sub view_head3 {
83     "\n<span size='large'>" . $_[1]->title->present ($_[0]) . "</span>\n\n"
84     . $_[1]->content->present ($_[0])
85     };
86    
87     sub view_over {
88     local $indent = $indent + $_[1]->indent;
89     $_[1]->content->present ($_[0])
90     }
91    
92     package CFClient::Database;
93    
94     our @ISA = BerkeleyDB::Btree::;
95    
96     sub get($$) {
97     my $data;
98    
99     $_[0]->db_get ($_[1], $data) == 0
100     ? $data
101     : ()
102     }
103    
104     my %DB_SYNC;
105    
106     sub put($$$) {
107     my ($db, $key, $data) = @_;
108    
109     $DB_SYNC{$db} = AnyEvent->timer (after => 5, cb => sub { $db->db_sync });
110    
111     $db->db_put ($key => $data)
112     }
113    
114     package CFClient;
115 root 1.52
116 root 1.5 sub find_rcfile($) {
117     my $path;
118    
119 root 1.46 for (grep !ref, @INC) {
120 root 1.22 $path = "$_/CFClient/resources/$_[0]";
121 root 1.5 return $path if -r $path;
122     }
123    
124     die "FATAL: can't find required file $_[0]\n";
125     }
126    
127     sub read_cfg {
128     my ($file) = @_;
129    
130     open CFG, $file
131     or return;
132    
133     my $CFG;
134    
135     local $/;
136     $CFG = eval <CFG>;
137    
138     $::CFG = $CFG;
139    
140     close CFG;
141     }
142    
143     sub write_cfg {
144     my ($file) = @_;
145    
146     open CFG, ">$file"
147     or return;
148    
149     {
150 elmex 1.9 require Data::Dumper;
151 root 1.5 local $Data::Dumper::Purity = 1;
152     $::CFG->{VERSION} = $::VERSION;
153     print CFG Data::Dumper->Dump ([$::CFG], [qw/CFG/]);
154     }
155    
156     close CFG;
157     }
158    
159 root 1.77 our $DB_ENV;
160    
161 root 1.76 {
162     use strict;
163    
164 root 1.87 mkdir "$Crossfire::VARDIR/cfplus", 0777;
165 root 1.77 my $recover = $BerkeleyDB::db_version >= 4.4
166     ? eval "DB_REGISTER | DB_RECOVER"
167     : 0;
168    
169     $DB_ENV = new BerkeleyDB::Env
170 root 1.76 -Home => "$Crossfire::VARDIR/cfplus",
171     -Cachesize => 1_000_000,
172     -ErrFile => "$Crossfire::VARDIR/cfplus/errorlog.txt",
173 root 1.39 # -ErrPrefix => "DATABASE",
174 root 1.76 -Verbose => 1,
175 root 1.77 -Flags => DB_CREATE | DB_RECOVER | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN | $recover,
176 root 1.78 -SetFlags => DB_AUTO_COMMIT | DB_LOG_AUTOREMOVE,
177 root 1.76 or die "unable to create/open database home $Crossfire::VARDIR/cfplus: $BerkeleyDB::Error";
178     }
179 root 1.34
180     sub db_table($) {
181 root 1.38 my ($table) = @_;
182    
183     $table =~ s/([^a-zA-Z0-9_\-])/sprintf "=%x=", ord $1/ge;
184 root 1.76
185 root 1.34 new CFClient::Database
186     -Env => $DB_ENV,
187 root 1.38 -Filename => $table,
188     # -Filename => "database",
189     # -Subname => $table,
190 root 1.51 -Property => DB_CHKSUM,
191 root 1.34 -Flags => DB_CREATE | DB_UPGRADE,
192 root 1.76 or die "unable to create/open database table $_[0]: $BerkeleyDB::Error"
193 root 1.34 }
194    
195 root 1.89 my $pod_cache = db_table "pod_cache";
196 root 1.52
197 root 1.89 sub load_pod($$$$) {
198     my ($path, $filtertype, $filterversion, $filtercb) = @_;
199 root 1.52
200 root 1.89 stat $path
201     or die "$path: $!";
202 root 1.60
203 root 1.89 my $phash = join ",", $filterversion, $CFClient::PodToPango::VERSION, (stat _)[7,9];
204 root 1.60
205 root 1.89 my ($chash, $pom) = eval { @{ Storable::thaw $pod_cache->get ("$path/$filtertype") } };
206 root 1.52
207 root 1.89 return $pom if $chash eq $phash;
208 root 1.52
209 root 1.89 my $pod = do {
210     local $/;
211     open my $pod, "<:utf8", $_[0]
212     or die "$_[0]: $!";
213     <$pod>
214     };
215 root 1.52
216 root 1.89 #utf8::downgrade $pod;
217 root 1.52
218 root 1.89 $pom = $filtercb-> (Pod::POM->new->parse_text ($pod));
219 root 1.52
220 root 1.89 $pod_cache->put ("$path/$filtertype" => Storable::nfreeze [$phash, $pom]);
221 root 1.52
222 root 1.89 $pom
223 root 1.53 }
224    
225 root 1.89 sub pod_to_pango($) {
226     my ($pom) = @_;
227 root 1.52
228 root 1.89 $pom->present ("CFClient::PodToPango")
229 root 1.52 }
230    
231 root 1.89 sub pod_to_pango_list($) {
232     my ($pom) = @_;
233 root 1.34
234 root 1.89 [
235     map s/^(\s*)// && [40 * length $1, length $_ ? $_ : " "],
236     split /\n/, $pom->present ("CFClient::PodToPango")
237     ]
238 root 1.34 }
239    
240 root 1.62 package CFClient::Item;
241    
242 root 1.71 use strict;
243     use Crossfire::Protocol::Constants;
244    
245 elmex 1.84 my $last_enter_count = 1;
246    
247 root 1.62 sub desc_string {
248     my ($self) = @_;
249    
250     my $desc =
251     $self->{nrof} < 2
252     ? $self->{name}
253     : "$self->{nrof} × $self->{name_pl}";
254    
255 root 1.71 $self->{flags} & F_OPEN
256 root 1.62 and $desc .= " (open)";
257 root 1.71 $self->{flags} & F_APPLIED
258 root 1.62 and $desc .= " (applied)";
259 root 1.71 $self->{flags} & F_UNPAID
260 root 1.62 and $desc .= " (unpaid)";
261 root 1.71 $self->{flags} & F_MAGIC
262 root 1.62 and $desc .= " (magic)";
263 root 1.71 $self->{flags} & F_CURSED
264 root 1.62 and $desc .= " (cursed)";
265 root 1.71 $self->{flags} & F_DAMNED
266 root 1.62 and $desc .= " (damned)";
267 root 1.71 $self->{flags} & F_LOCKED
268 root 1.62 and $desc .= " *";
269    
270     $desc
271     }
272    
273     sub weight_string {
274     my ($self) = @_;
275    
276     my $weight = ($self->{nrof} || 1) * $self->{weight};
277    
278     $weight < 0 ? "?" : $weight * 0.001
279     }
280    
281 elmex 1.84 sub do_n_dialog {
282     my ($cb) = @_;
283    
284     my $w = new CFClient::UI::FancyFrame;
285     $w->add (my $vb = new CFClient::UI::VBox x => "center", y => "center");
286     $vb->add (new CFClient::UI::Label text => "Enter item count:");
287     $vb->add (my $entry = new CFClient::UI::Entry
288     text => $last_enter_count,
289     on_activate => sub {
290     my ($entry) = @_;
291     $last_enter_count = $entry->get_text;
292     $cb->($last_enter_count);
293     $w->hide;
294     $w = undef;
295     }
296     );
297 root 1.93 $entry->grab_focus;
298 elmex 1.84 $w->show;
299    
300     }
301    
302 root 1.62 sub update_widgets {
303     my ($self) = @_;
304    
305 root 1.92 # necessary to avoid cyclic references
306     Scalar::Util::weaken $self;
307    
308 root 1.63 my $button_cb = sub {
309     my (undef, $ev, $x, $y) = @_;
310    
311 elmex 1.84 my $targ = $::CONN->{player}{tag};
312 root 1.63
313 elmex 1.84 if ($self->{container} == $::CONN->{player}{tag}) {
314     $targ = $::CONN->{open_container};
315     }
316 root 1.63
317 elmex 1.84 if (($ev->{mod} & CFClient::KMOD_SHIFT) && $ev->{button} == 1) {
318 root 1.79 $::CONN->send ("move $targ $self->{tag} 0")
319     if $targ || !($self->{flags} & F_LOCKED);
320 elmex 1.86 } elsif (($ev->{mod} & CFClient::KMOD_SHIFT) && $ev->{button} == 2) {
321     $self->{flags} & F_LOCKED
322     ? $::CONN->send ("lock " . pack "CN", 0, $self->{tag})
323     : $::CONN->send ("lock " . pack "CN", 1, $self->{tag})
324 root 1.63 } elsif ($ev->{button} == 1) {
325     $::CONN->send ("examine $self->{tag}");
326     } elsif ($ev->{button} == 2) {
327     $::CONN->send ("apply $self->{tag}");
328     } elsif ($ev->{button} == 3) {
329     my @menu_items = (
330     ["examine", sub { $::CONN->send ("examine $self->{tag}") }],
331     ["mark", sub { $::CONN->send ("mark ". pack "N", $self->{tag}) }],
332     ["apply", sub { $::CONN->send ("apply $self->{tag}") }],
333     (
334 root 1.71 $self->{flags} & F_LOCKED
335 root 1.63 ? (
336     ["unlock", sub { $::CONN->send ("lock " . pack "CN", 0, $self->{tag}) }],
337     )
338     : (
339     ["lock", sub { $::CONN->send ("lock " . pack "CN", 1, $self->{tag}) }],
340 elmex 1.64 ["drop", sub { $::CONN->send ("move $::CONN->{open_container} $self->{tag} 0") }],
341 elmex 1.84 ["move n",
342     sub {
343     do_n_dialog (sub { $::CONN->send ("move $targ $self->{tag} $_[0]") })
344     }
345     ]
346 root 1.63 )
347     ),
348     );
349    
350     CFClient::UI::Menu->new (items => \@menu_items)->popup ($ev);
351     }
352    
353     1
354     };
355    
356 root 1.62 my $tooltip_std = "<small>"
357     . "Left click - examine item\n"
358     . "Shift-Left click - " . ($self->{container} ? "move or drop" : "take") . " item\n"
359     . "Middle click - apply\n"
360 elmex 1.86 . "Shift-Middle click - lock/unlock\n"
361 root 1.62 . "Right click - further options"
362     . "</small>\n";
363    
364 root 1.63 $self->{face_widget} ||= new CFClient::UI::Face
365     can_events => 1,
366     can_hover => 1,
367 root 1.67 anim => $self->{anim},
368 root 1.66 animspeed => $self->{animspeed}, # TODO# must be set at creation time
369 root 1.72 on_button_down => $button_cb,
370 root 1.63 ;
371 root 1.62 $self->{face_widget}{face} = $self->{face};
372     $self->{face_widget}{anim} = $self->{anim};
373 root 1.65 $self->{face_widget}{animspeed} = $self->{animspeed};
374 root 1.62 $self->{face_widget}->set_tooltip (
375     "<b>Face/Animation.</b>\n"
376     . "Item uses face #$self->{face}. "
377     . ($self->{animspeed} ? "Item uses animation #$self->{anim} at " . (1 / $self->{animspeed}) . "fps. " : "Item is not animated. ")
378     . "\n\n$tooltip_std"
379     );
380    
381 root 1.63 $self->{desc_widget} ||= new CFClient::UI::Label
382     can_events => 1,
383     can_hover => 1,
384     ellipsise => 2,
385 root 1.68 align => -1,
386 root 1.72 on_button_down => $button_cb,
387 root 1.63 ;
388     my $desc = CFClient::Item::desc_string $self;
389     $self->{desc_widget}->set_text ($desc);
390     $self->{desc_widget}->set_tooltip ("<b>$desc</b>.\n$tooltip_std");
391    
392     $self->{weight_widget} ||= new CFClient::UI::Label
393     can_events => 1,
394     can_hover => 1,
395     ellipsise => 0,
396 root 1.68 align => 0,
397 root 1.72 on_button_down => $button_cb,
398 root 1.63 ;
399 root 1.62 $self->{weight_widget}->set_text (CFClient::Item::weight_string $self);
400    
401     $self->{weight_widget}->set_tooltip (
402     "<b>Weight</b>.\n"
403     . ($self->{weight} >= 0 ? "One item weighs $self->{weight}g. " : "You have no idea how much this weighs. ")
404     . ($self->{nrof} ? "You have $self->{nrof} of it. " : "Item cannot stack with others of it's kind. ")
405     . "\n\n$tooltip_std"
406     );
407     }
408    
409 elmex 1.81 package CFClient::Binder;
410 elmex 1.73
411     my @ALLOWED_MODIFIER_KEYS = (
412 root 1.85 CFClient::SDLK_LSHIFT,
413     CFClient::SDLK_LCTRL ,
414     CFClient::SDLK_LALT ,
415     CFClient::SDLK_LMETA ,
416    
417     CFClient::SDLK_RSHIFT,
418     CFClient::SDLK_RCTRL ,
419     CFClient::SDLK_RALT ,
420     CFClient::SDLK_RMETA ,
421 elmex 1.73 );
422    
423     my %ALLOWED_MODIFIERS = (
424 root 1.85 CFClient::KMOD_LSHIFT => "LSHIFT",
425     CFClient::KMOD_LCTRL => "LCTRL",
426     CFClient::KMOD_LALT => "LALT",
427     CFClient::KMOD_LMETA => "LMETA",
428    
429     CFClient::KMOD_RSHIFT => "RSHIFT",
430     CFClient::KMOD_RCTRL => "RCTRL",
431     CFClient::KMOD_RALT => "RALT",
432     CFClient::KMOD_RMETA => "RMETA",
433 elmex 1.73 );
434    
435     my %DIRECT_BIND_CHARS = map { $_ => 1 } qw/0 1 2 3 4 5 6 7 8 9/;
436     my @DIRECT_BIND_KEYS = (
437     CFClient::SDLK_F1,
438     CFClient::SDLK_F2,
439     CFClient::SDLK_F3,
440     CFClient::SDLK_F4,
441     CFClient::SDLK_F5,
442     CFClient::SDLK_F6,
443     CFClient::SDLK_F7,
444     CFClient::SDLK_F8,
445     CFClient::SDLK_F9,
446     CFClient::SDLK_F10,
447     CFClient::SDLK_F11,
448     CFClient::SDLK_F12,
449     CFClient::SDLK_F13,
450     CFClient::SDLK_F14,
451     CFClient::SDLK_F15,
452     );
453    
454     # this binding dialog asks for a key-combo to be pressed
455 elmex 1.81 # and if successful it calls the $cb with $mod and $sym as args.
456 elmex 1.73 sub open_binding_dialog {
457 elmex 1.81 my ($cb) = @_;
458 elmex 1.73
459     my $w = new CFClient::UI::FancyFrame
460 elmex 1.80 title => "Bind Action",
461     x => "center",
462     y => "center";
463 elmex 1.73
464     $w->add (my $vb = new CFClient::UI::VBox);
465     $vb->add (new CFClient::UI::Label
466     text => "Press a modifier (CTRL, ALT and/or SHIFT) and a key."
467     ."You can only bind 0-9 and F1-F15 without modifiers."
468     );
469     $vb->add (my $entry = new CFClient::UI::Entry
470     text => "",
471     on_key_down => sub {
472     my ($entry, $ev) = @_;
473    
474     my $mod = $ev->{mod};
475     my $sym = $ev->{sym};
476    
477     # XXX: This seems a little bit hackisch to me, but i have to ignore them
478     if (grep { $_ == $sym } @ALLOWED_MODIFIER_KEYS) {
479     return;
480     }
481    
482     if ($mod == CFClient::KMOD_NONE
483     and not $DIRECT_BIND_CHARS{chr ($ev->{unicode})}
484     and not grep { $sym == $_ } @DIRECT_BIND_KEYS)
485     {
486     $::STATUSBOX->add (
487     "Can't bind key ".CFClient::SDL_GetKeyName ($sym)
488     ." directly without modifier! It would damage the completer handling."
489     );
490     return;
491     }
492    
493 root 1.93 $entry->grab_focus;
494 elmex 1.73
495 elmex 1.81 $cb->($mod, $sym);
496 elmex 1.73
497     $w->destroy
498     });
499    
500 root 1.93 $entry->grab_focus;
501 elmex 1.73 $w->show;
502     }
503    
504     sub keycombo_to_name {
505     my ($mod, $sym) = @_;
506    
507     my $mods = join '+',
508     map { $ALLOWED_MODIFIERS{$_} }
509 elmex 1.81 grep { ($_ + 0) & ($mod + 0) }
510 elmex 1.73 keys %ALLOWED_MODIFIERS;
511     $mods .= "+" if $mods ne '';
512    
513     return $mods . CFClient::SDL_GetKeyName ($sym);
514     }
515    
516 root 1.1 1;
517    
518     =back
519    
520     =head1 AUTHOR
521    
522     Marc Lehmann <schmorp@schmorp.de>
523     http://home.schmorp.de/
524    
525     =cut
526