ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/kgsueme/bin/kgsueme
Revision: 1.25
Committed: Fri May 30 15:50:20 2003 UTC (20 years, 11 months ago) by pcg
Branch: MAIN
Changes since 1.24: +116 -64 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 pcg 1.25 #!/usr/bin/perl
2 pcg 1.1
3     use Gtk;
4     use Gtk::Gdk;
5 pcg 1.12 use Gtk::Gdk::Pixbuf;
6     #use Gtk::Gdk::ImlibImage;
7 pcg 1.1
8     use KGS::Protocol;
9     use KGS::Listener::Debug;
10    
11 pcg 1.16 use Audio::Data;
12     use Audio::Play;
13    
14 pcg 1.1 use IO::Socket::INET;
15 pcg 1.25 use List::Util;
16 pcg 1.1 use Errno;
17    
18 pcg 1.12 init Gtk;
19 pcg 1.1
20 pcg 1.3 $HACK = 1; # do NEVER enable. ;)
21    
22 pcg 1.25 if ($HACK) {
23     $KGS::debug = 1;
24     }
25    
26 pcg 1.2 our $config;
27 pcg 1.16 our $LIBDIR = ".";
28     our $IMGDIR = "$LIBDIR/images";
29     our $SNDDIR = "$LIBDIR/sounds";
30 pcg 1.7
31     sub load_img {
32 pcg 1.12 new_from_file Gtk::Gdk::Pixbuf "$IMGDIR/$_[0]"
33     # load_image Gtk::Gdk::ImlibImage "$IMGDIR/$_[0]"
34 pcg 1.7 or die "$IMGDIR/$_[0]: $!";
35     }
36    
37 pcg 1.17 my @fontchars = ('A' .. 'Z', 0 .. 9);
38    
39 pcg 1.12 our @black_img = load_img "b-01.png";
40     our @white_img = map +(load_img "w-0$_.png"), 1,2,3,4,5;
41     our @triangle_img = map +(load_img "triangle-$_.png"), qw(b w);
42     our @square_img = map +(load_img "square-$_.png"), qw(b w);
43     our @circle_img = map +(load_img "circle-$_.png"), qw(b w);
44     our $board_img = load_img "woodgrain-01.jpg";
45 pcg 1.2
46 pcg 1.17 our @font = (
47     [map +(load_img "font/$_-black.png"), @fontchars],
48     [map +(load_img "font/$_-white.png"), @fontchars],
49     );
50     our %fontmap;
51     @fontmap{@fontchars} = (0..25 + 10);
52     @fontmap{'a' .. 'z'} = (0..25);
53    
54 pcg 1.1 {
55 pcg 1.16 #my $audioserver = new Audio::Play(0);
56     my %sound;
57     $SIG{CHLD} = 'IGNORE';
58    
59     for (qw(alarm warning move)) {
60     local $/;
61     open my $snd, "<", "$SNDDIR/$_"
62     or die "$SNDDIR: $!";
63     binmode $snd;
64    
65     $sound{$_} = new Audio::Data;
66     $sound{$_}->Load($snd);
67     }
68    
69     sub play_sound {
70     if (fork == 0) {
71 pcg 1.19 if (my $audioserver = new Audio::Play(1)) {
72     $audioserver->play ($sound{$_[0]});
73     }
74 pcg 1.16 Gtk->_exit(0);
75     }
76     }
77     }
78    
79     {
80 pcg 1.1 use Storable ();
81     use Scalar::Util ();
82    
83     my $staterc = "$ENV{HOME}/.kgsueme";
84    
85     my $state = -r $staterc ? Storable::retrieve($staterc) : {};
86     my @widgets;
87    
88 pcg 1.2 $config = $state->{config} ||= {};
89    
90 pcg 1.23 $config{speed} = 1;#d# optimize for speed or memory?
91     $config{conserve_memory} = 0;
92    
93 pcg 1.1 # grr... more gtk+ brokenness
94     my %get = (
95 pcg 1.8 hpane_position => sub { ($_[0]->children)[0]->allocation->[2] },
96     vpane_position => sub { ($_[0]->children)[0]->allocation->[3] },
97 pcg 1.1 window_size => sub { [ @{$_[0]->allocation}[2,3] ] },
98 pcg 1.16 #window_pos => sub { die KGS::Listener::Debug::dumpval [ $_[0]->get_root_origin ] },
99 pcg 1.1 clist_column_widths => sub {
100     $_[0]{column_widths};
101     },
102     );
103    
104     my %set = (
105     hpane_position => sub { $_[0]->set_position($_[1]) },
106     vpane_position => sub { $_[0]->set_position($_[1]) },
107     window_size => sub { $_[0]->set_default_size(@{$_[1]}) },
108     #window_pos => sub { $_[0]->set_uposition(@{$_[1]}) if @{$_[1]} },
109     clist_column_widths => sub {
110     my ($w, $v) = @_;
111     $v->[$_] && $w->set_column_width($_, $v->[$_]) for 0..$#$v;
112     $w->{column_widths} = $v;
113     $w->signal_connect(resize_column => sub { $v->[$_[1]] = $_[2]; });
114     },
115     );
116    
117     sub state {
118     my ($widget, $class, $instance, %attr) = @_;
119    
120     while (my ($k, $v) = each %attr) {
121     my ($set, $get) = $k =~ /=/ ? split /=/, $k : ($k, $k);
122     $v = $state->{$class}{"*"}{$get} if exists $state->{$class}{"*"}{$get};
123     $v = $state->{$class}{$instance}{$get} if exists $state->{$class}{$instance}{$get};
124     $set{$get} ? $set{$get}->($widget, $v) : $widget->set($set => $v);
125     }
126    
127     $widget = [$widget, $class, $instance, \%attr];
128     Scalar::Util::weaken $widget->[0];
129    
130     @widgets = (grep $_->[0], @widgets, $widget);
131     }
132    
133     sub save_state {
134     for (@widgets) {
135     if ($_->[0]) {
136     my ($widget, $class, $instance, $attr) = @$_;
137 pcg 1.7
138     $widget->realize;
139    
140 pcg 1.1 while (my ($k, $v) = each %$attr) {
141     my ($set, $get) = $k =~ /=/ ? split /=/, $k : ($k, $k);
142     $v = $get{$get} ? $get{$get}->($widget) : $widget->get($get);
143    
144     $state->{$class}{"*"}{$get} = $v;
145     $state->{$class}{$instance}{$get} = $v;
146     }
147     }
148     ::status("save_state", "layout saved");
149     }
150    
151     Storable::nstore($state, $staterc);
152     }
153     }
154    
155     # make a clist unselectable
156     sub clist_autosort {
157     my $w = shift;
158     my ($c, $o) = (-1);
159     for (0..$w->columns-1) {
160     $w->signal_connect(click_column => sub {
161     if ($_[1] != $c) {
162     $c = $_[1];
163     $o = 0;
164     } else {
165     $o = !$o;
166     }
167     $w->set_sort_column($c);
168     $w->set_sort_type($o ? "descending" : "ascending");
169     $w->sort;
170     });
171     }
172    
173     }
174    
175     {
176     my $main = new kgsueme;
177    
178     my %context_id;
179    
180     sub status {
181     my ($type, $text) = @_;
182    
183     $main->{status}->pop($context_id{$type}) if $context_id{$type};
184     $main->{status}->push($context_id{$type} ||= $main->{status}->get_context_id($type), $text) if $text;
185     }
186     }
187    
188 pcg 1.12 if (0) {
189 pcg 1.17 use KGS::Constants;
190    
191 pcg 1.25 for (19) {
192     my $board = new game size => $_;
193     $board->{board} = new KGS::Game::Board;
194     $board->{board}{board}[0][0] = MARK_B;
195     $board->{board}{board}[1][1] = MARK_GRAY_B | MARK_SMALL_W;
196     $board->{board}{board}[2][2] = MARK_W | MARK_TRIANGLE;
197     $board->{board}{board}[1][2] = MARK_B | MARK_LABEL;
198     $board->{board}{label}[1][2] = "198";
199     $board->{board}{board}[0][2] = MARK_W | MARK_LABEL;
200     $board->{board}{label}[0][2] = "AWA";
201     $board->{window}->show_all;
202     }
203 pcg 1.12 }
204    
205 pcg 1.1 main Gtk;
206    
207     #############################################################################
208    
209     package kgsueme;
210    
211     use base KGS::Listener;
212    
213     sub new {
214     my $self = shift;
215     $self = $self->SUPER::new(@_);
216    
217     $self->{conn} = new KGS::Protocol;
218    
219     KGS::Listener::Debug->new->listen($self->{conn}); #d# debug only :)
220    
221 pcg 1.25 $self->listen($self->{conn}, "login");
222 pcg 1.1
223 pcg 1.4 $self->{roomlist} = new roomlist conn => $self->{conn};
224    
225 pcg 1.1 $self->{window} = new Gtk::Window 'toplevel';
226     $self->{window}->set_title('kgsueme');
227     ::state $self->{window}, "main::window", undef, window_size => [400, 100];
228 pcg 1.7 $self->{window}->signal_connect(delete_event => sub { main_quit Gtk; 1 });
229 pcg 1.1
230     $self->{window}->add(my $vbox = new Gtk::VBox);
231    
232 pcg 1.4 $vbox->pack_start(($buttonbox = new Gtk::HButtonBox), 0, 1, 0);
233     $buttonbox->set_spacing(0);
234 pcg 1.1
235 pcg 1.2 my $button = sub {
236 pcg 1.4 $buttonbox->add(my $button = new Gtk::Button $_[0]);
237 pcg 1.2 signal_connect $button clicked => $_[1];
238     };
239 pcg 1.1
240 pcg 1.4 $button->("Login", sub { $self->login; });
241     $button->("Roomlist", sub { $self->{roomlist}->show; });
242 pcg 1.2 $button->("Save Config & Layout", sub { ::save_state });
243 pcg 1.4 $button->("Quit", sub { main_quit Gtk });
244 pcg 1.1
245     $vbox->pack_start((my $hbox = new Gtk::HBox), 0, 1, 0);
246    
247     $hbox->add(new Gtk::Label "Login");
248    
249     $hbox->add($self->{login} = new_with_max_length Gtk::Entry 12);
250 pcg 1.2 $self->{login}->set_text($::config->{login});
251 pcg 1.3
252     if ($::HACK) {
253     $self->{login}->signal_connect(activate => sub {
254     $self->{conn}{name} = $self->{login}->get_text;
255     });
256     }
257 pcg 1.1
258     $hbox->add(new Gtk::Label "Password");
259 pcg 1.2 $hbox->add($self->{password} = new Gtk::Entry);
260     $self->{password}->set_visibility(0);
261 pcg 1.1
262     $vbox->pack_start(($self->{status} = new Gtk::Statusbar), 0, 1, 0);
263    
264     $self->{window}->show_all;
265    
266     $self;
267     }
268    
269     sub login {
270     my ($self) = @_;
271    
272 pcg 1.2 $self->{conn}->disconnect;
273    
274 pcg 1.1 # initialize new socket and connection
275     my $sock = new IO::Socket::INET PeerHost => "kgs.kiseido.com", PeerPort => "2379"
276     or die;
277    
278     $sock->blocking(1);
279     $self->{conn}->handshake($sock);
280     $sock->blocking(0);
281    
282     my $input; $input = input_add Gtk::Gdk fileno $sock, "read", sub {
283     # this is dorked
284     my $buf;
285     if (0 >= sysread $sock, $buf, 16384
286     and !$!{EINTR} and !$!{EAGAIN}) {
287     input_remove Gtk::Gdk $input;
288     $self->event_disconnect;
289     }
290     $self->{conn}->feed_data($buf);
291     };
292    
293     # now login
294 pcg 1.2 $self->{conn}->login($self->{login}->get_text, $self->{password}->get_text);
295 pcg 1.1 }
296    
297     sub inject_login {
298     my ($self, $msg) = @_;
299    
300     ::status("login", "logged in as '$self->{conn}{name}' with status '$msg->{result}'");
301 pcg 1.2 $::config->{login} = $self->{conn}{name};
302 pcg 1.1
303 pcg 1.4 if ($msg->{success}) {
304     for (keys %{$::config->{rooms}}) {
305     $self->{roomlist}->join_room($_);
306     }
307     }
308 pcg 1.1 }
309    
310     sub event_disconnect { }
311    
312     #############################################################################
313    
314     package roomlist;
315    
316     use base KGS::Listener::Roomlist;
317    
318     sub new {
319     my $self = shift;
320     $self = $self->SUPER::new(@_);
321    
322     $self->listen($self->{conn});
323    
324     $self->{window} = new Gtk::Window 'toplevel';
325     $self->{window}->set_title('KGS Rooms');
326     ::state $self->{window}, "roomlist::window", undef, window_size => [400, 200];
327    
328 pcg 1.7 $self->{window}->signal_connect(delete_event => sub { $self->{window}->hide; 1 });
329 pcg 1.1
330     $self->{window}->add(my $vbox = new Gtk::VBox);
331    
332     $vbox->pack_start((my $sw = new Gtk::ScrolledWindow), 1, 1, 0);
333     $sw->set_policy("automatic", "always");
334    
335     $sw->add($self->{roomlist} = new_with_titles Gtk::CList "Group", "Room Name", "Users", "Games", "Flags", "Channel");
336     $self->{roomlist}->set_selection_mode('multiple');
337     ::clist_autosort $self->{roomlist};
338     ::state $self->{roomlist}, "roomlist::roomlist", undef, clist_column_widths => [20, 200];
339    
340     $self->{roomlist}->signal_connect(select_row => sub {
341     my $room = $self->{roomlist}->get_row_data($_[1])
342     or return;
343     $self->{roomlist}->unselect_all;
344 pcg 1.4 $self->join_room($room->{channel});
345 pcg 1.1 });
346    
347     $self;
348     }
349    
350 pcg 1.4 sub join_room {
351     my ($self, $channel) = @_;
352    
353     $self->{room}{$channel} ||= room->new(channel => $channel, conn => $self->{conn}, users => {});
354     $self->{room}{$channel}->join;
355     }
356    
357 pcg 1.1 sub show {
358     my ($self, $msg) = @_;
359    
360     $self->msg(list_rooms => group => $_) for 0..5; # fetch all room names (should not!)
361     $self->{window}->show_all;
362     }
363    
364 pcg 1.9 sub event_update_rooms {
365 pcg 1.1 my ($self) = @_;
366    
367     $self->{event_update} ||= Gtk->timeout_add(200, sub {
368     my $l = $self->{roomlist};
369    
370     $l->freeze;
371     my $pos = $l->get_vadjustment->get_value;
372     $l->clear;
373    
374     my $row = 0;
375     for (values %{$self->{rooms}}) {
376     $l->append($_->{group}, $_->{name}, $_->{users}, $_->{games}, $_->{flags}, $_->{channel});
377     $l->set_row_data($row++, $_);
378     }
379     $l->sort;
380     $l->get_vadjustment->set_value($pos);
381     $l->thaw;
382    
383     delete $self->{event_update};
384     0;
385     });
386     }
387    
388     #############################################################################
389    
390     package room;
391    
392     use base KGS::Listener::Room;
393    
394     sub new {
395     my $self = shift;
396     $self = $self->SUPER::new(@_);
397    
398 pcg 1.25 $self->listen($self->{conn}, qw(msg_room:));
399 pcg 1.1
400     $self->{window} = new Gtk::Window 'toplevel';
401     $self->{window}->set_title("KGS Room $self->{name}");
402     ::state $self->{window}, "room::window", $self->{name}, window_size => [600, 400];
403    
404 pcg 1.7 $self->{window}->signal_connect(delete_event => sub { $self->part; 1 });
405 pcg 1.1
406     $self->{window}->add(my $hpane = new Gtk::HPaned);
407     ::state $hpane, "room::hpane", $self->{name}, hpane_position => 200;
408    
409     $hpane->add(my $vpane = new Gtk::VPaned);
410     ::state $vpane, "room::vpane", $self->{name}, vpane_position => 200;
411    
412     $vpane->add(my $sw = new Gtk::ScrolledWindow);
413     $sw->set_policy("automatic", "always");
414    
415     $sw->add($self->{gamelist} = new_with_titles Gtk::CList "T", "Black", "White", "Rules", "Notes");
416     ::clist_autosort $self->{gamelist};
417     ::state $self->{gamelist}, "room::gamelist", $self->{name}, clist_column_widths => [20, 120, 120, 120];
418    
419     $self->{gamelist}->signal_connect(select_row => sub {
420     my $game = $self->{gamelist}->get_row_data($_[1])
421     or return;
422     $self->{game}{$game->{channel}} ||= new game %$game, conn => $self->{conn};
423     $self->{game}{$game->{channel}}->join;
424     $self->{gamelist}->unselect_all;
425     });
426    
427     $vpane->add(my $vbox = new Gtk::VBox);
428    
429     $vbox->pack_start((my $sw = new Gtk::ScrolledWindow), 1, 1, 0);
430     $sw->set_policy("automatic", "always");
431    
432     $sw->add($self->{text} = new Gtk::Text);
433    
434     $vbox->pack_start(($self->{entry} = new Gtk::Entry), 0, 1, 0);
435     $self->{entry}->signal_connect(activate => sub {
436     my $text = $self->{entry}->get_text;
437     $self->say($text) if $text =~ /\S/;
438     $self->{entry}->set_text("");
439     });
440    
441     $hpane->add(my $sw = new Gtk::ScrolledWindow);
442     $sw->set_policy("automatic", "always");
443    
444     $sw->add($self->{userlist} = new_with_titles Gtk::CList "User", "Rank", "Flags");
445     ::clist_autosort $self->{userlist};
446     ::state $self->{userlist}, "room::userlist", $self->{name}, clist_column_widths => [120, 30];
447    
448     $self;
449     }
450    
451 pcg 1.25 sub join {
452     my ($self) = @_;
453     $self->SUPER::join;
454    
455     $self->{window}->show_all;
456     }
457    
458     sub part {
459     my ($self) = @_;
460     $self->SUPER::part;
461    
462     delete $::config->{rooms}{$self->{channel}};
463     $self->{window}->hide_all;
464     $self->event_update_users;
465     $self->event_update_games;
466     }
467    
468     sub inject_msg_room {
469     my ($self, $msg) = @_;
470    
471     $self->{text}->insert(undef, undef, undef, "\n$msg->{name}: $msg->{message}");
472     }
473    
474 pcg 1.9 sub event_update_users {
475 pcg 1.1 my ($self) = @_;
476    
477     $self->{event_update} ||= Gtk->timeout_add(200, sub {
478     my $l = $self->{userlist};
479    
480     $l->freeze;
481     my $pos = $l->get_vadjustment->get_value;
482     $l->clear;
483    
484     my $row = 0;
485     for (values %{$self->{users}}) {
486     $l->append($_->{name});
487     $l->set_row_data($row++, $_);
488     }
489     $l->sort;
490     $l->get_vadjustment->set_value($pos);
491     $l->thaw;
492    
493     delete $self->{event_update};
494     0;
495     });
496     }
497    
498     sub event_update_games {
499     my ($self) = @_;
500    
501     $self->{event_update_games} ||= Gtk->timeout_add(200, sub {
502     my $l = $self->{gamelist};
503    
504     $l->freeze;
505     my $pos = $l->get_vadjustment->get_value;
506     $l->clear;
507    
508     my $row = 0;
509     for (values %{$self->{games}}) {
510     $l->append($_->type, $_->user0, $_->user1, $_->rules, $_->notes);
511     $l->set_row_data($row++, $_);
512     }
513     $l->sort;
514     $l->get_vadjustment->set_value($pos);
515     $l->thaw;
516    
517     delete $self->{event_update_games};
518     0;
519     });
520     }
521    
522 pcg 1.4 sub event_join {
523     my ($self) = @_;
524     $self->SUPER::event_join;
525    
526     $::config->{rooms}{$self->{channel}} = 1;
527 pcg 1.1 }
528    
529     sub event_update_roominfo {
530     my ($self) = @_;
531    
532 pcg 1.7 $self->{text}->insert(undef, undef, undef, "\n$self->{owner}: $self->{description}\n");
533 pcg 1.1 }
534    
535     #############################################################################
536    
537     package game;
538    
539 pcg 1.7 use KGS::Constants;
540 pcg 1.9 use KGS::Game::Board;
541 pcg 1.7
542 pcg 1.1 use base KGS::Listener::Game;
543     use base KGS::Game;
544    
545     sub new {
546     my $self = shift;
547     $self = $self->SUPER::new(@_);
548    
549     $self->listen($self->{conn});
550    
551     $self->{window} = new Gtk::Window 'toplevel';
552 pcg 1.25 my $title = $self->{channel} ? $self->user0." ".$self->user1 : "Game Window";
553     $self->{window}->set_title("KGS Game $title");
554 pcg 1.1 ::state $self->{window}, "game::window", undef, window_size => [600, 500];
555    
556 pcg 1.7 $self->{window}->signal_connect(delete_event => sub { $self->part; 1 });
557 pcg 1.1
558     $self->{window}->add(my $hpane = new Gtk::HPaned);
559     ::state $hpane, "game::hpane", undef, hpane_position => 500;
560    
561 pcg 1.25 $hpane->pack1(my $vbox = new Gtk::VBox);
562    
563     $vbox->pack_start((my $frame = new Gtk::Frame), 0, 1, 0);
564    
565     {
566     $frame->add(my $vbox = new Gtk::VBox);
567     $vbox->add($self->{title} = new Gtk::Label $title);
568    
569     $self->{moveadj} = new Gtk::Adjustment 0, 0, 0, 1, 10, 0;
570     $vbox->add(my $scale = new Gtk::HScale $self->{moveadj});
571     $scale->set_draw_value (1);
572     $scale->set_digits (0);
573     $scale->set_value_pos('top');
574    
575     $self->{moveadj}->signal_connect (value_changed => sub {
576     $self->{board} = new KGS::Game::Board $self->{size};
577     $self->{board}->interpret_path ([@{$self->{path}}[0 .. $self->{moveadj}->value - 1]]);
578    
579     $self->repaint_board;
580    
581     # force a redraw (not perfect(?))
582     expose_event($self->{canvas}, $self, { area => [0, 0, @{$self->{canvas}->allocation}[2,3]] });
583    
584     $self->{text}->backward_delete($self->{text}->get_length);
585     $self->{text}->insert(undef, undef, undef, KGS::Listener::Debug::dumpval([$self->{board}{time},$self->{board}{captures}]). $self->{board}{comment});
586     });
587     }
588    
589 pcg 1.12 Gtk::Widget->push_visual (Gtk::Gdk::Rgb->get_visual);
590     Gtk::Widget->push_colormap (Gtk::Gdk::Rgb->get_cmap);
591 pcg 1.25 $vbox->pack_start(($self->{canvas} = new Gtk::DrawingArea), 1, 1, 0);
592 pcg 1.12 Gtk::Widget->pop_colormap;
593     Gtk::Widget->pop_visual;
594 pcg 1.7
595 pcg 1.12 $self->{canvas}->signal_connect(configure_event => \&configure_event, $self);
596     $self->{canvas}->signal_connect(expose_event => \&expose_event, $self);
597 pcg 1.1
598 pcg 1.11 $hpane->pack2((my $vpane = new Gtk::VPaned), 0, 0);
599 pcg 1.1 ::state $vpane, "game", $self->{name}, vpane_position => 80;
600    
601     $vpane->add(my $sw = new Gtk::ScrolledWindow);
602     $sw->set_policy("automatic", "always");
603    
604     $sw->add($self->{userlist} = new_with_titles Gtk::CList "User", "Rank", "Flags");
605     ::clist_autosort $self->{userlist};
606     ::state $self->{userlist}, "room::userlist", $self->{name}, clist_column_widths => [120, 30];
607    
608     $vpane->add(my $vbox = new Gtk::VBox);
609    
610     $vbox->pack_start((my $sw = new Gtk::ScrolledWindow), 1, 1, 0);
611     $sw->set_policy("automatic", "always");
612    
613     $sw->add($self->{text} = new Gtk::Text);
614    
615     $vbox->pack_start(($self->{entry} = new Gtk::Entry), 0, 1, 0);
616     $self->{entry}->signal_connect(activate => sub {
617     my $text = $self->{entry}->get_text;
618     # add message
619     $self->{entry}->set_text("");
620     });
621    
622     $self;
623     }
624    
625 pcg 1.9 sub event_update_users {
626 pcg 1.1 my ($self) = @_;
627    
628 pcg 1.9 room::event_update_users $self;
629 pcg 1.1 }
630    
631     sub join {
632     my ($self) = @_;
633     $self->SUPER::join;
634    
635     $self->{window}->show_all;
636     }
637    
638     sub part {
639     my ($self) = @_;
640     $self->SUPER::part;
641    
642 pcg 1.10 $self->{window}->hide;
643 pcg 1.1 }
644    
645 pcg 1.12 sub configure_event {
646     my ($widget, $self, $event) = @_;
647 pcg 1.13 delete $self->{stack};
648 pcg 1.18 delete $self->{background};
649 pcg 1.12 $self->repaint_board;
650     1;
651     }
652    
653 pcg 1.17 sub INTERP_NEAREST (){ 1 }
654 pcg 1.12 sub INTERP_TILES (){ 1 }
655     sub INTERP_BILINEAR (){ 2 }
656     sub INTERP_HYPER (){ 3 }
657    
658     sub new_pixbuf {
659 pcg 1.17 my ($w, $h, $alpha, $clear) = @_;
660 pcg 1.12
661 pcg 1.17 my $pixbuf = new Gtk::Gdk::Pixbuf 'rgb', $alpha, 8, $w, $h;
662 pcg 1.12
663     if ($clear) { # damn, need to clear it ourselves
664     my $row = "\x00\x00\x00\x00" x $w;
665     $pixbuf->put_pixels ($row, $_, 0) for 0 .. $h - 1;
666     }
667    
668     $pixbuf;
669     }
670    
671     sub scale_pixbuf {
672     my ($src, $w, $h, $mode) = @_;
673    
674 pcg 1.17 my $dst = new_pixbuf $w, $h, 1;
675 pcg 1.12
676     $src->scale(
677     $dst, 0, 0, $w, $h, 0, 0,
678     $w / $src->get_width, $h / $src->get_height,
679     $mode,
680     );
681    
682     $dst;
683     }
684    
685     # create a stack of stones
686     sub create_stack {
687 pcg 1.17 my ($self, $mark, $size, $rand) = @_;
688 pcg 1.12
689 pcg 1.20 my $shadow = $size * 0.06;
690    
691 pcg 1.13 my $c = \$self->{stack}{$mark};
692 pcg 1.12 unless ($$c) {
693     for my $stone ($mark & (MARK_W | MARK_GRAY_W) ? @::white_img : @::black_img) {
694 pcg 1.20 my $base = new_pixbuf $size + $shadow, $size + $shadow, 1, 1;
695    
696     # zeroeth the shadow
697 pcg 1.24 if ($mark & (MARK_B | MARK_W)) {
698     $::black_img[0]->composite (
699     $base, $shadow, $shadow, $size, $size, $shadow-0.5, $shadow-0.5,
700     $size / $stone->get_width, $size / $stone->get_height,
701     $::config{speed} ? INTERP_NEAREST : INTERP_TILES, 128
702     );
703     }
704 pcg 1.18
705     # first the big stones
706     for ([MARK_B, 255],
707     [MARK_W, 255],
708     [MARK_GRAY_B, 128],
709     [MARK_GRAY_W, 128]) {
710     my ($mask, $alpha) = @$_;
711     if ($mark & $mask) {
712     $stone->composite (
713     $base, 0, 0, $size, $size, -0.5, -0.5,
714     $size / $stone->get_width, $size / $stone->get_height,
715 pcg 1.23 $::config{speed} ? INTERP_NEAREST : INTERP_HYPER, $alpha
716 pcg 1.18 );
717     }
718 pcg 1.12 }
719    
720 pcg 1.18 # then the samll stones
721 pcg 1.15 for ([MARK_SMALL_B, $::black_img[$rand % @::black_img]],
722     [MARK_SMALL_W, $::white_img[$rand % @::white_img]]) {
723 pcg 1.12 my ($mask, $img) = @$_;
724     if ($mark & $mask) {
725     $img->composite (
726 pcg 1.18 $base, ($size / 4) x2, (int ($size / 2 + 0.5)) x2, ($size / 4 - 0.5) x 2,
727 pcg 1.14 $size / $img->get_width / 2, $size / $img->get_height / 2,
728 pcg 1.23 $::config{speed} ? INTERP_NEAREST : INTERP_HYPER, 192
729 pcg 1.12 );
730     }
731     }
732    
733 pcg 1.18 # and lastly any markers (labels NYI)
734 pcg 1.12 my $dark_bg = ! ! ($mark & (MARK_B | MARK_GRAY_B));
735    
736     for ([MARK_CIRCLE, $::circle_img[$dark_bg]],
737     [MARK_TRIANGLE, $::triangle_img[$dark_bg]],
738     [MARK_SQUARE, $::square_img[$dark_bg]]) {
739     my ($mask, $img) = @$_;
740     if ($mark & $mask) {
741     $img->composite (
742 pcg 1.18 $base, 0, 0, $size, $size, -0.5, -0.5,
743     $size / $img->get_width, $size / $img->get_height,
744 pcg 1.23 $::config{speed} ? INTERP_NEAREST : INTERP_HYPER, 255
745 pcg 1.12 );
746     }
747     }
748    
749 pcg 1.17 push @$$c, $base;
750 pcg 1.12 }
751     }
752    
753 pcg 1.17 $$c->[$rand % @$$c];
754     }
755    
756     sub pixbuf_text {
757     my ($pixbuf, $colour, $x, $y, $height, $text) = @_;
758    
759 pcg 1.25 my @c = grep $_,
760     map $::font[$colour][$::fontmap{$_}],
761     split //, $text;
762    
763     if (@c) {
764     my $spacing = $height * 0.1;
765     my $s = $height / List::Util::max map $_->get_height, @c;
766     my $W = List::Util::sum map $_->get_width, @c;
767 pcg 1.17
768 pcg 1.25 $x -= ($W * $s + $spacing * (@c - 1)) * 0.5;
769 pcg 1.17 $y -= $height * 0.5;
770    
771 pcg 1.25 for (@c) {
772     my $w = $_->get_width * $s;
773     $_->composite ($pixbuf,
774     $x, $y, $w+0.999, $height+0.999, $x, $y, $s, $s,
775     $::config{speed} ? INTERP_NEAREST : INTERP_BILINEAR, 255);
776 pcg 1.17
777 pcg 1.25 $x += $w + $spacing;
778     }
779 pcg 1.17 }
780     }
781    
782     my $black_pb;
783    
784     sub pixbuf_rect {
785     my ($pb, $x1, $y1, $x2, $y2) = @_;
786     # we fake lines by... an unspeakable method :/
787     unless ($black_pb) {
788     $black_pb = new_pixbuf 1, 1, 0, 0;
789     $black_pb->put_pixels ("\x44\x11\x11", 0, 0);
790     }
791    
792 pcg 1.18 $black_pb->composite ($pb, $x1, $y1, $x2 - $x1 + 1, $y2 - $y1 + 1, $x1, $y1, 1, 1, INTERP_NEAREST, 192);
793 pcg 1.12 }
794    
795     sub repaint_board {
796 pcg 1.1 my ($self) = @_;
797 pcg 1.12 my $canvas = $self->{canvas};
798    
799 pcg 1.16 return unless $self->{board};
800    
801 pcg 1.12 my ($w, $h) = @{$canvas->allocation}[2,3];
802    
803     my $s = $w > $h ? $h : $w;
804    
805     $self->{offsets} = [int (($w - $s) / 2), int (($h - $s) / 2), $s];
806    
807 pcg 1.18 my $size = $self->{size};
808    
809 pcg 1.25 my $border = int ($s / ($size + 3) * 0.5);
810 pcg 1.18 my $s2 = $s - $border * 2;
811 pcg 1.25 my $edge = int ($s2 / ($size + 1) * 0.97);
812 pcg 1.18 my $ofs = int ($edge / 2);
813    
814 pcg 1.25 my @k = map int ($s2 * $_ / ($size+1) + $border + 0.5), 0 .. $size;
815 pcg 1.18
816 pcg 1.23 my $pixbuf;
817 pcg 1.1
818 pcg 1.23 if ($self->{background}) {
819     $pixbuf = $self->{background}->copy;
820     } else {
821 pcg 1.12 my ($bw, $bh) = ($::board_img->get_width, $::board_img->get_height);
822    
823 pcg 1.17 if ($s < $bw && $s < $bh) {
824 pcg 1.23 $pixbuf = new_pixbuf $s, $s, $::config{conserve_memory} ? 0 : 1, 0;
825 pcg 1.17 $::board_img->copy_area (0, 0, $s, $s, $pixbuf, 0, 0);
826     } else {
827 pcg 1.23 $pixbuf = scale_pixbuf $::board_img, $s, $s, $::config{speed} ? INTERP_NEAREST : INTERP_TILES;
828 pcg 1.17 }
829 pcg 1.12
830 pcg 1.25 my $linew = int ($s / 25 / $size);
831 pcg 1.12
832 pcg 1.18 for my $i (1 .. $size) {
833     pixbuf_rect $pixbuf, $k[$i] - $linew, $k[1] - $linew, $k[$i] + $linew, $k[$size] + $linew;
834     pixbuf_rect $pixbuf, $k[1] - $linew, $k[$i] - $linew, $k[$size] + $linew, $k[$i] + $linew;
835    
836 pcg 1.25 # 38 max, but we allow a bit more
837     my $label = (qw(- A B C D E F G H J K L M N O P Q R S T U V W X Y Z
838     AA BB CC DD EE FF GG HH JJ KK LL MM NN OO PP QQ RR SS TT UU VV WW XX YY ZZ))[$i];
839    
840     pixbuf_text $pixbuf, 0, $k[$i], $border, $ofs, $label;
841     pixbuf_text $pixbuf, 0, $k[$i], $s2 + $border, $ofs, $label;
842     pixbuf_text $pixbuf, 0, $border, $k[$i], $ofs, $size - $i + 1;
843     pixbuf_text $pixbuf, 0, $s2 + $border, $k[$i], $ofs, $size - $i + 1;
844 pcg 1.12
845 pcg 1.18 $a++;
846     $a++ if $a eq "I"; # not correct, instead of AA AB, we should get HH JJ KK...
847     }
848 pcg 1.12
849 pcg 1.23 unless ($::config->{conserve_memory}) {
850     $self->{background} = $pixbuf;
851     $pixbuf = $pixbuf->copy;
852     }
853 pcg 1.18 }
854 pcg 1.12
855 pcg 1.23 $self->{pixbuf} = $pixbuf;
856 pcg 1.12
857     # hoshi-points(!)#d#
858     # caching of empty board gfx(!)#d#
859 pcg 1.7
860 pcg 1.16 for my $x (1 .. $size) {
861     for my $y (1 .. $size) {
862     my $mark = $self->{board}{board}[$x-1][$y-1];
863    
864     if ($mark) {
865     my ($dx, $dy) = ($k[$x] - $ofs, $k[$y] - $ofs);
866 pcg 1.17 my $pb = $self->create_stack($mark, $edge, $x * 17 + $y * 11 );
867 pcg 1.16
868 pcg 1.20 $pb->composite ($pixbuf, $dx, $dy, $pb->get_width, $pb->get_height,
869 pcg 1.23 $dx, $dy, 1, 1, $::config{speed} ? INTERP_NEAREST : INTERP_NEAREST, 255);
870 pcg 1.25
871     # labels are handled here because they are quite rare
872     if ($mark & MARK_LABEL) {
873     pixbuf_text $pixbuf, $mark & (MARK_W | MARK_GRAY_W) ? 0 : 1,
874     $k[$x], $k[$y], $ofs * 0.7,
875     $self->{board}{label}[$x-1][$y-1];
876     }
877 pcg 1.17
878     #my ($pm, $bm) = $self->create_stack($gc, $mark, $edge, $x * 17 + $y * 11 );
879    
880     #$gc->set_clip_mask ($bm);
881     #$gc->set_clip_origin ($dx, $dy);
882     #$pixmap->draw_pixmap ($gc, $pm, 0, 0, $dx, $dy, $edge, $edge);
883 pcg 1.12 }
884 pcg 1.1 }
885     }
886 pcg 1.17 }
887    
888     sub expose_event {
889     my ($widget, $self, $event) = @_;
890    
891     $self->{pixbuf} or return;
892    
893     my ($ox, $oy, $s) = @{$self->{offsets}};
894    
895     my ($x, $y, $w, $h) =
896     @{Gtk::Gdk::Rectangle->intersect(
897     $event->{area},
898     [$ox, $oy, $s, $s]
899     )};
900    
901     if (defined $x) {
902     $self->{pixbuf}->render_to_drawable ($self->{canvas}->window, $self->{canvas}->style->white_gc,
903     $x - $ox, $y - $oy, $x, $y, $w, $h);
904     #$self->{canvas}->window->draw_pixmap (
905     # $self->{canvas}->style->white_gc,
906     # $self->{pixmap},
907     # $x - $ox, $y - $oy, $x, $y, $w, $h,
908     #);
909     }
910     1;
911 pcg 1.12 }
912    
913     sub event_update_tree {
914     my ($self) = @_;
915    
916 pcg 1.25 $self->{path} = $self->get_path;
917    
918     my $move = @{$self->{path}};
919 pcg 1.12
920 pcg 1.25 $self->{moveadj}->upper($move);
921 pcg 1.9
922 pcg 1.25 if ($self->{moveadj}->value >= $move - 1 || !$self->{moveadj}->value) {
923     $self->{moveadj}->set_value ($move);
924     }
925 pcg 1.16 }
926    
927     sub event_move {
928     ::play_sound "move";
929 pcg 1.1 }
930    
931     1;
932    
933    
934