ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/EditAction.pm
Revision: 1.37
Committed: Thu Jun 1 14:00:04 2006 UTC (18 years ago) by elmex
Branch: MAIN
Changes since 1.36: +8 -70 lines
Log Message:
fixed a (design) bug with the editactions which caused
a messup with undo/change events. as the code became cleaner
and clearer i suspect it works now as expected :-)

File Contents

# User Rev Content
1 elmex 1.1 package GCE::EditAction;
2    
3     =head1 NAME
4    
5     GCE::EditActions - this is the abstraction of edit actions (placing, deleting, etc.)
6    
7     =cut
8    
9     use Gtk2;
10     use Gtk2::Gdk::Keysyms;
11     use Gtk2::SimpleMenu;
12    
13     use Crossfire;
14     use Crossfire::MapWidget;
15    
16     use strict;
17    
18     sub new {
19     my $class = shift;
20     my $self = { @_ };
21     bless $self, $class;
22 elmex 1.6 $self->init;
23 elmex 1.1 return $self;
24     }
25    
26 elmex 1.10 sub name { } # a unique name for this tool (for storing it in a hash in the main window)
27    
28 elmex 1.7 sub tool_widget { if ($_[1]) { $_[0]->{widget} = $_[1] } $_[0]->{widget} }
29 elmex 1.6 sub init { }
30    
31 elmex 1.1 sub want_cursor { 1 }
32    
33     sub special_arrow { }
34    
35     # edits one tile of the map
36     sub edit_one {
37     my ($self, $x, $y) = @_;
38     # do one edition
39     }
40    
41     # edits a selection
42     sub edit_selection {
43     }
44    
45     # abstraction for edit_one and edit_selection ?
46     # takes selection if present
47     sub edit {
48 elmex 1.10 my ($self, $map, $x, $y) = @_;
49 elmex 1.1 }
50    
51     sub begin {
52 elmex 1.10 my ($self, $map, $x, $y) = @_;
53 root 1.3 $map->change_begin (ref $self);
54 elmex 1.1 }
55 root 1.3
56 elmex 1.1 sub end {
57 root 1.3 my ($self, $map) = @_;
58    
59     if (my $changeset = $map->change_end) {
60     splice @{ $map->{undo_stack} ||= [] },
61 root 1.4 $map->{undo_stack_pos}++, 1e6,
62 root 1.3 $changeset;
63    
64     #TODO: limit undo stack size to some preconfigured limit
65     }
66 elmex 1.1 }
67    
68 elmex 1.7 package GCE::EditAction::RadioModed;
69    
70     our @ISA = qw/GCE::EditAction/;
71    
72     sub add_mode_button {
73     my ($self, $vb, $lbl, $mode, $default) = @_;
74    
75     $vb->pack_start (my $b = Gtk2::RadioButton->new ($self->{place_radio_grp}, $lbl), 0, 1, 0);
76     unless (defined $self->{place_radio_grp}) {
77     $self->{place_radio_grp} = $b->get_group;
78    
79     unless (defined $default) {
80     $b->set_active (1);
81     $self->set_mode ($mode);
82     }
83     }
84     if ($default) {
85     $b->set_active (1);
86     $self->set_mode ($mode);
87     }
88     $b->signal_connect (clicked => sub {
89     $self->set_mode ($mode);
90     });
91     }
92    
93     sub set_mode {
94     my ($self, $mode) = @_;
95     $self->{place_mode} = $mode;
96     }
97    
98     sub get_mode {
99     my ($self) = @_;
100     $self->{place_mode}
101     }
102    
103     sub init {
104     my ($self) = @_;
105    
106     die "Implement me!!";
107    
108     # my $vb = new Gtk2::VBox;
109     # $self->_add_mode_button ($vb, "auto", "auto");
110     # $self->_add_mode_button ($vb, "top", "top");
111     # $self->_add_mode_button ($vb, "above floor", "above");
112     # $self->_add_mode_button ($vb, "below floor", "below");
113     # $self->_add_mode_button ($vb, "bottom", "bottom");
114     #
115     # $self->{widget} = $vb;
116     }
117 elmex 1.1
118     package GCE::EditAction::Pick;
119 elmex 1.34 use GCE::ArchRef;
120 elmex 1.7 use strict;
121 elmex 1.1
122 elmex 1.23 our @ISA = qw/GCE::EditAction::RadioModed/;
123 elmex 1.1
124 elmex 1.10 sub name { 'pick' }
125    
126 elmex 1.7 sub want_cursor { 0 }
127    
128 elmex 1.1 sub special_arrow { 'GDK_HAND2' }
129    
130 elmex 1.23 sub init {
131     my ($self) = @_;
132    
133     my $vb = new Gtk2::VBox;
134     $self->{widget} = $vb;
135     }
136    
137 elmex 1.1 sub edit {
138 elmex 1.10 my ($self, $map, $x, $y) = @_;
139 elmex 1.1
140     my $cstack = $map->get ($x, $y);
141    
142 elmex 1.34 return unless @$cstack;
143    
144 elmex 1.1 my $arch = $cstack->[-1];
145    
146     # virtual... grmbl....
147     # FIXME: I have to patch the stack of the real arch??? argl.. how??
148 elmex 1.35 my ($ox, $oy) = ($x, $y);
149 elmex 1.1 if ($arch->{_virtual}) {
150 root 1.3 $x = $arch->{virtual_x};
151     $y = $arch->{virtual_y};
152 elmex 1.1 $arch = $arch->{_virtual};
153 elmex 1.6 $cstack = $map->get ($x, $y);
154 elmex 1.34 # XXX: This heavily blows up if $arch isn't on $cstack now.. and it actually really does :(
155 elmex 1.1 }
156    
157 elmex 1.34 my $aref =
158     GCE::ArchRef->new (
159     arch => $arch,
160     cb => sub {
161     $map->change_begin ('attredit');
162     $map->change_stack ($x, $y, $cstack);
163    
164     if (my $changeset = $map->change_end) {
165     splice @{ $map->{undo_stack} ||= [] },
166     $map->{undo_stack_pos}++, 1e6,
167     $changeset;
168     }
169     }
170     );
171    
172     $::MAINWIN->update_attr_editor ($aref);
173 elmex 1.35 $::MAINWIN->update_stack_view ($map, $ox, $oy);
174 elmex 1.23 }
175 elmex 1.1
176 elmex 1.23 sub end {}
177 elmex 1.1
178 elmex 1.13 package GCE::EditAction::Perl;
179    
180     use GCE::Util;
181     use Gtk2;
182     use strict;
183    
184     our @ISA = qw/GCE::EditAction/;
185    
186     sub name { 'perl' }
187    
188 elmex 1.15 sub special_arrow { 'GDK_HEART' }
189    
190 elmex 1.13 sub init {
191     my ($self) = @_;
192    
193     my $vb = new Gtk2::VBox;
194 elmex 1.20 $vb->pack_start (my $combo = Gtk2::ComboBox->new_text, 0, 1, 0);
195 elmex 1.13 $vb->pack_start (my $sw = Gtk2::ScrolledWindow->new, 1, 1, 0);
196     $sw->add ($self->{txt} = Gtk2::TextView->new);
197    
198 elmex 1.20 my $code = {};
199     for (['unique floor' => 'my $i = 0; for (@$os) { $_->{is_floor} and $as->[$i]->{unique} = 1; $i++ }']) {
200     $combo->append_text ($_->[0]);
201     $code->{$_->[0]} = $_->[1];
202     }
203    
204     $combo->signal_connect (changed => sub {
205     my ($combo) = @_;
206     my $str = $combo->get_active_text;
207     $self->{txt}->get_buffer->set_text ($code->{$str});
208     });
209    
210 elmex 1.13 $self->tool_widget ($vb);
211     }
212    
213     sub want_cursor { 0 }
214    
215     sub edit {
216     my ($self, $map, $x, $y) = @_;
217    
218     my $pick = $::MAINWIN->get_pick;
219     my $as = $map->get ($x, $y);
220    
221     $as = $self->eval ($map, $pick, $as, $x, $y);
222     $map->change_stack ($x, $y, $as); # insert_arch_stack_layer ($as, $arch));
223     }
224    
225     sub eval {
226 elmex 1.20 my ($self, $map, $pick, $as, $x, $y) = @_;
227 elmex 1.13 my $buf = $self->{txt}->get_buffer;
228     my $code = $buf->get_text ($buf->get_start_iter, $buf->get_end_iter, 0);
229 elmex 1.20 my $f_idx = stack_find_floor ($as, 'from_top');
230     my $w_idx = stack_find_wall ($as, 'from_top');
231     my $os = [ map { $Crossfire::ARCH{$_->{_name}} } @$as ];
232    
233     unless (arch_is_floor ($as->[$f_idx])) { $f_idx = undef; }
234     unless (arch_is_floor ($as->[$w_idx])) { $w_idx = undef; }
235 elmex 1.13
236     eval $code;
237 elmex 1.20 return $as;
238 elmex 1.13 }
239    
240 elmex 1.18 package GCE::EditAction::FollowExit;
241     use Storable qw/dclone/;
242     use File::Spec::Functions;
243     use GCE::Util;
244     use Gtk2;
245     use strict;
246    
247     our @ISA = qw/GCE::EditAction/;
248    
249     sub name { 'place' }
250    
251     sub init {
252     my ($self) = @_;
253    
254     my $vb = new Gtk2::VBox;
255    
256     $self->tool_widget ($vb);
257     }
258    
259     sub want_cursor { 0 }
260    
261     sub edit {
262 elmex 1.21 my ($self, $map, $x, $y, $mape) = @_;
263 elmex 1.18
264     my $as = $map->get ($x, $y);
265    
266     my $exit;
267 elmex 1.27 for my $arch (@$as) {
268     if ($arch->{_virtual}) {
269     $arch = $arch->{_virtual};
270     }
271     if (arch_is_exit ($arch)) {
272     $exit = $arch;
273 elmex 1.18 }
274     }
275    
276 elmex 1.32 if ($exit and $exit->{slaying} !~ /^!/) {
277 elmex 1.22 my $dest = map2abs ($exit->{slaying}, $mape);
278 elmex 1.32 # XXX: Replace with statusbar message
279     unless (-e $dest) {
280     warn "Couldn't find '$dest'";
281     return
282     }
283 elmex 1.22 $::MAINWIN->open_map_editor ($dest);
284 elmex 1.18 }
285     }
286    
287 elmex 1.1 package GCE::EditAction::Place;
288 root 1.28
289 elmex 1.14 use Storable qw/dclone/;
290 elmex 1.2 use GCE::Util;
291 elmex 1.6 use Gtk2;
292     use strict;
293 elmex 1.2
294 elmex 1.7 our @ISA = qw/GCE::EditAction::RadioModed/;
295 elmex 1.6
296 elmex 1.10 sub name { 'place' }
297    
298 elmex 1.6 sub init {
299     my ($self) = @_;
300    
301     my $vb = new Gtk2::VBox;
302 root 1.28
303 elmex 1.7 $self->add_mode_button ($vb, "auto", "auto");
304     $self->add_mode_button ($vb, "top", "top");
305     $self->add_mode_button ($vb, "above floor", "above");
306     $self->add_mode_button ($vb, "below floor", "below");
307     $self->add_mode_button ($vb, "bottom", "bottom");
308 elmex 1.6
309 elmex 1.7 $self->tool_widget ($vb);
310 elmex 1.6 }
311    
312 elmex 1.1 sub want_cursor { 0 }
313    
314 root 1.28 # 1 up 2 right 4 down 8 left
315     my @join_ext = (
316     "0", # 0
317     "1_2", # 1
318     "1_4", # 2
319     "2_2_1", # 3
320     "1_1", # 4
321     "2_1_1", # 5
322     "2_2_2", # 6
323     "3_2", # 7
324     "1_3", # 8
325     "2_2_4", # 9
326     "2_1_2", # 10
327     "3_1", # 11
328     "2_2_3", # 12
329     "3_4", # 13
330     "3_3", # 14
331     "4", # 15
332     );
333    
334     sub autojoin {
335     my ($map, $pick, $x1, $y1, $x2, $y2) = @_;
336    
337     my $dx = $x2 - $x1;
338     my $dy = $y2 - $y1;
339    
340     my $dir = $dy ? ($dy == -1 ? 1 : $dy == 1 ? 4 : return)
341     : ($dx == -1 ? 8 : $dx == 1 ? 2 : $dx == 0 ? 0 : return);
342    
343     my $as = $map->get ($x1, $y1);
344    
345     (my $base = $pick->{_name}) =~ s/_0$//;
346    
347     for my $idx (0 .. $#$as) {
348     my $arch = $as->[$idx];
349     for my $dirs (0..15) {
350     my $name = $arch->{_name};
351    
352     if ($arch->{_name} eq "$base\_$join_ext[$dirs]") {
353     $dirs |= $dir;
354    
355     my $name = "$base\_$join_ext[$dirs]";
356    
357     if ($Crossfire::ARCH{$name}) {
358     %$arch = ( _name => $name );
359     $map->change_stack ($x1, $y1, $as);
360    
361     return 1;
362     }
363     }
364     }
365     }
366    
367     return 0;
368     }
369    
370 elmex 1.1 sub edit {
371 elmex 1.10 my ($self, $map, $x, $y) = @_;
372 elmex 1.1
373 elmex 1.10 my $pick = $::MAINWIN->get_pick;
374     my $as = $map->get ($x, $y);
375 elmex 1.1
376 root 1.28 my $autojoin = $pick->{_name} =~ /_0$/
377     && $self->get_mode eq "auto";
378    
379     autojoin $map, $pick, @{$self->{last_pos}}, $x, $y
380     if $autojoin && $self->{last_pos};
381    
382     if (!$autojoin
383     || !($self->{last_pos} ? autojoin $map, $pick, $x, $y, @{$self->{last_pos}},
384     : autojoin $map, $pick, $x, $y, $x, $y)) {
385     $self->stack_action ($as, dclone $pick);
386     $map->change_stack ($x, $y, $as);
387     autojoin $map, $pick, $x, $y, @{$self->{last_pos}}
388     if $autojoin && $self->{last_pos};
389     }
390    
391     $self->{last_pos} = [$x, $y];
392 elmex 1.1 }
393    
394 elmex 1.17 sub end {
395     my ($self, $map, $x, $y, $mape) = @_;
396 elmex 1.37
397 elmex 1.22 # now actualize stack and attr editor
398     $::MAINWIN->update_stack_view ($map, $x, $y);
399    
400     my $cstack = $map->get ($x, $y);
401    
402     my $arch = $cstack->[-1];
403    
404 root 1.28 delete $self->{last_pos};
405    
406 elmex 1.22 # virtual... grmbl....
407     # FIXME: I have to patch the stack of the real arch??? argl.. how??
408     if ($arch->{_virtual}) {
409     $x = $arch->{virtual_x};
410     $y = $arch->{virtual_y};
411     $arch = $arch->{_virtual};
412     $cstack = $map->get ($x, $y);
413     }
414    
415 elmex 1.17 $self->SUPER::end ($map, $x, $y, $mape);
416     }
417    
418 elmex 1.6 sub stack_action {
419     my ($self, $stack, $arch) = @_;
420    
421 elmex 1.7 my $m = $self->get_mode;
422 elmex 1.6
423     if ($m eq 'top') {
424     if (@$stack == 0 or $stack->[-1]->{_name} ne $arch->{_name}) {
425     push @$stack, $arch;
426     }
427    
428     } elsif ($m eq 'bottom') {
429     if (@$stack == 0 or $stack->[0]->{_name} ne $arch->{_name}) {
430     unshift @$stack, $arch;
431     }
432    
433     } elsif ($m eq 'above') {
434     my $fidx = stack_find_floor ($stack, 'from_top');
435    
436     if (@$stack == 0
437     or not ($stack->[$fidx + 1])
438     or $stack->[$fidx + 1]->{_name} ne $arch->{_name})
439     {
440     splice (@$stack, $fidx + 1, 0, $arch);
441     }
442    
443     } elsif ($m eq 'below') {
444     my $fidx = stack_find_floor ($stack, 'from_bottom');
445    
446     if (@$stack == 0
447     or $fidx == 0
448     or not ($stack->[$fidx - 1])
449     or $stack->[$fidx - 1]->{_name} ne $arch->{_name})
450     {
451     splice (@$stack, $fidx, 0, $arch);
452     }
453    
454     } elsif ($m eq 'auto') {
455     my $fidx = stack_find_floor ($stack, 'from_top');
456     my $widx = stack_find_wall ($stack);
457    
458     if (arch_is_floor ($arch)) { # we want to place a floor tile
459    
460     if (arch_is_floor ($stack->[$fidx])) { # replace
461     $stack->[$fidx] = $arch;
462    
463     } else { # insert on bottom
464     unshift @$stack, $arch;
465     }
466    
467     } elsif (arch_is_wall ($arch)) { # we want to place a wall
468    
469     if (arch_is_wall ($stack->[$widx])) { # replace
470     $stack->[$widx] = $arch;
471    
472     } else { # insert above floor
473     splice (@$stack, $fidx + 1, 0, $arch);
474     }
475    
476     } else {
477    
478     if (arch_is_wall ($stack->[$widx])) {
479 elmex 1.9 # if we have a wall above the floor, replace it with the to place item
480     $stack->[$widx] = $arch;
481 elmex 1.6 return;
482     }
483    
484     if (@$stack == 0
485     or not ($stack->[-1])
486     or $stack->[-1]->{_name} ne $arch->{_name})
487     {
488     push @$stack, $arch;
489 elmex 1.36
490     } elsif ($stack->[-1]->{_name} eq $arch->{_name}) {
491     $stack->[-1] = $arch;
492 elmex 1.6 }
493     }
494     }
495     }
496    
497 elmex 1.12 package GCE::EditAction::Select;
498 elmex 1.11 use GCE::Util;
499     use Gtk2;
500 elmex 1.12 use Crossfire;
501     use Storable qw/dclone/;
502 elmex 1.11 use strict;
503    
504     our @ISA = qw/GCE::EditAction::RadioModed/;
505    
506 elmex 1.12 sub name { 'select' }
507 elmex 1.11
508 elmex 1.15 sub special_arrow { 'GDK_CIRCLE' }
509    
510 elmex 1.11 sub init {
511     my ($self) = @_;
512    
513     my $vb = new Gtk2::VBox;
514    
515 elmex 1.19 $vb->pack_start (my $bt = Gtk2::Button->new_with_mnemonic ("_copy"), 0, 1, 0);
516 elmex 1.12 $bt->signal_connect (clicked => sub { $self->copy });
517     $vb->pack_start ($self->{paste_top} = Gtk2::CheckButton->new ('paste on top'), 0, 1, 0);
518 elmex 1.19 $vb->pack_start (my $bt = Gtk2::Button->new_with_mnemonic ("paste (_v)"), 0, 1, 0);
519 elmex 1.12 $bt->signal_connect (clicked => sub { $self->paste });
520     $vb->pack_start (Gtk2::HSeparator->new, 0, 1, 0);
521     $self->add_mode_button ($vb, "place", "place");
522     $self->add_mode_button ($vb, "erase", "erase");
523 elmex 1.15 $self->add_mode_button ($vb, "perl", "perl");
524 elmex 1.19 $vb->pack_start (my $bt = Gtk2::Button->new_with_mnemonic ("i_nvoke"), 0, 1, 0);
525 elmex 1.12 $bt->signal_connect (clicked => sub { $self->invoke });
526 elmex 1.11
527     $self->tool_widget ($vb);
528     }
529    
530 elmex 1.12 sub copy {
531     my ($self) = @_;
532    
533     return unless $self->{selection}->{a};
534     my ($x1, $y1) = @{$self->{selection}->{a}};
535     my ($x2, $y2) = @{$self->{selection}->{b}};
536    
537     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
538     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
539    
540     my $map = $self->{selection}->{map};
541    
542     $self->{copy_coords} = [$x1, $y1, $x2, $y2];
543     $self->{copy};
544     for (my $x = $x1; $x <= $x2; $x++) {
545     for (my $y = $y1; $y <= $y2; $y++) {
546     $self->{copy}->[$x - $x1]->[$y - $y1] = $map->get ($x, $y);
547     }
548     }
549     }
550    
551     sub paste {
552 elmex 1.23 my ($self, $map, $xp, $yp) = @_;
553 elmex 1.12
554     return unless $self->{selection}->{a};
555    
556     my ($x1, $y1);
557    
558 elmex 1.15 if (defined $xp) {
559 elmex 1.12 ($x1, $y1) = ($xp, $yp);
560     } else {
561     ($x1, $y1) = @{$self->{selection}->{a}};
562     }
563    
564 elmex 1.23 $map ||= $self->{selection}->{map};
565 elmex 1.12
566     my $p_o_top = $self->{paste_top}->get_active * 1;
567    
568     my $w = $self->{copy_coords}->[2] - $self->{copy_coords}->[0];
569     my $h = $self->{copy_coords}->[3] - $self->{copy_coords}->[1];
570     $self->{copy};
571     $self->SUPER::begin ($map, $x1, $y1);
572     for (my $x = $x1; $x <= ($x1 + $w); $x++) {
573     for (my $y = $y1; $y <= ($y1 + $h); $y++) {
574     my $cstck = $map->get ($x, $y);
575    
576     if ($p_o_top) {
577     push @$cstck, @{dclone ($self->{copy}->[$x - $x1]->[$y - $y1] || [])};
578     $map->change_stack ($x, $y, $cstck);
579     } else {
580     $map->change_stack ($x, $y, dclone ($self->{copy}->[$x - $x1]->[$y - $y1] || []));
581     }
582     }
583     }
584     $self->SUPER::end ($map);
585     $map->invalidate_all;
586     }
587    
588     sub invoke {
589     my ($self) = @_;
590    
591     return unless $self->{selection}->{a};
592     my ($x1, $y1) = @{$self->{selection}->{a}};
593     my ($x2, $y2) = @{$self->{selection}->{b}};
594    
595     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
596     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
597    
598     my $map = $self->{selection}->{map};
599    
600     my $m = $self->get_mode;
601     $self->SUPER::begin ($map, $x1, $y1);
602     for (my $x = $x1; $x <= $x2; $x++) {
603     for (my $y = $y1; $y <= $y2; $y++) {
604     if ($m eq 'place') {
605     $::MAINWIN->{edit_collection}{place}->edit ($map, $x, $y);
606     } elsif ($m eq 'erase') {
607     $::MAINWIN->{edit_collection}{erase}->edit ($map, $x, $y);
608 elmex 1.15 } elsif ($m eq 'perl') {
609     $::MAINWIN->{edit_collection}{perl}->edit ($map, $x, $y);
610 elmex 1.12 }
611     }
612     }
613     $self->SUPER::end ($map);
614     }
615    
616 elmex 1.11 sub want_cursor { 0 }
617    
618     sub begin {
619     my ($self, $map, $x, $y) = @_;
620    
621 elmex 1.29 if ($self->{selection}->{map}) {
622     $self->{selection}->{map}->overlay ('selection');
623     }
624 elmex 1.11 delete $self->{selection};
625    
626 elmex 1.12 $self->{selection}->{a} = [$x, $y];
627     }
628 elmex 1.11
629 elmex 1.12 sub end {
630 elmex 1.11 }
631    
632     sub edit {
633     my ($self, $map, $x, $y) = @_;
634    
635     $self->{selection}->{b} = [$x, $y];
636 elmex 1.12 $self->{selection}->{map} = $map;
637 elmex 1.29 $self->update_overlay ();
638 elmex 1.11 }
639    
640     sub update_overlay {
641 elmex 1.29 my ($self) = @_;
642    
643     my $map = $self->{selection}->{map};
644    
645     return unless (defined $self->{selection}->{a} and defined $self->{selection}->{b});
646 elmex 1.11
647 elmex 1.12 my ($x1, $y1) = @{$self->{selection}->{a}};
648     my ($x2, $y2) = @{$self->{selection}->{b}};
649    
650     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
651     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
652 elmex 1.11
653 elmex 1.12 my $w = ($x2 - $x1) + 1;
654     my $h = ($y2 - $y1) + 1;
655    
656     $map->overlay (selection =>
657     $x1 * TILESIZE, $y1 * TILESIZE,
658     $w * TILESIZE, $h * TILESIZE,
659     sub {
660     my ($self, $x, $y) = @_;
661     $self->{window}->draw_rectangle (
662     $_ & 1 ? $self->style->black_gc : $self->style->white_gc,
663     0,
664     $x + $_, $y + $_,
665     ($w * TILESIZE) - 1 - $_ * 2,
666     ($h * TILESIZE) - 1 - $_ * 2
667     ) for 0..3;
668     }
669     );
670 elmex 1.11 }
671    
672 elmex 1.1 package GCE::EditAction::Erase;
673 elmex 1.7 use GCE::Util;
674     use Gtk2;
675     use strict;
676 elmex 1.1
677 elmex 1.7 our @ISA = qw/GCE::EditAction::RadioModed/;
678 elmex 1.1
679 elmex 1.10 sub name { 'erase' }
680    
681 elmex 1.1 sub want_cursor { 0 }
682    
683 elmex 1.15 sub special_arrow { 'GDK_DIAMOND_CROSS' }
684 elmex 1.10
685 elmex 1.7 sub init {
686     my ($self) = @_;
687    
688     my $vb = new Gtk2::VBox;
689     $self->add_mode_button ($vb, "top", "top");
690     $self->add_mode_button ($vb, "walls", "walls");
691     $self->add_mode_button ($vb, "above floor", "above", 'default');
692     $self->add_mode_button ($vb, "floor", "floor");
693     $self->add_mode_button ($vb, "below floor", "below");
694     $self->add_mode_button ($vb, "bottom", "bottom");
695     $self->add_mode_button ($vb, "pick match", "match");
696    
697     $self->tool_widget ($vb);
698    
699 root 1.25 $vb->pack_start ($self->{no_wall_check} = Gtk2::CheckButton->new ("protect walls"), 0, 1, 0);
700     $vb->pack_start ($self->{no_monsters_check} = Gtk2::CheckButton->new ("protect monsters"), 0, 1, 0);
701 elmex 1.7 }
702    
703     sub check_excluded {
704     my ($self, $arch) = @_;
705    
706 elmex 1.37 my $r = ($self->{no_wall_check}->get_active && arch_is_wall ($arch))
707     || ($self->{no_monsters_check}->get_active && arch_is_monster ($arch));
708 elmex 1.7
709 elmex 1.37 return $r;
710 elmex 1.1 }
711    
712     sub edit {
713 elmex 1.10 my ($self, $map, $x, $y) = @_;
714 elmex 1.1
715     my $as = $map->get ($x, $y);
716 elmex 1.7 $self->stack_action ($as);
717 root 1.3 $map->change_stack ($x, $y, $as);
718 elmex 1.1 }
719    
720 elmex 1.7 sub stack_action {
721     my ($self, $stack) = @_;
722    
723     my $m = $self->get_mode;
724    
725     if ($m eq 'top') {
726     pop @$stack;
727    
728     } elsif ($m eq 'bottom') {
729     shift @$stack;
730    
731     } elsif ($m eq 'above') {
732     my $fidx = stack_find_floor ($stack, 'from_top');
733    
734     if (arch_is_floor ($stack->[$fidx]) and $stack->[$fidx + 1]) {
735     splice (@$stack, $fidx + 1, 1)
736     unless $self->check_excluded ($stack->[$fidx + 1])
737    
738     } elsif (not arch_is_floor ($stack->[$fidx])) {
739     splice (@$stack, $fidx, 1)
740     unless $self->check_excluded ($stack->[$fidx])
741    
742     }
743    
744     } elsif ($m eq 'below') {
745     my $fidx = stack_find_floor ($stack, 'from_bottom');
746    
747     if ($fidx > 0 and not arch_is_floor ($stack->[$fidx - 1])) {
748     splice (@$stack, $fidx - 1, 1)
749     unless $self->check_excluded ($stack->[$fidx - 1])
750    
751     } elsif (not arch_is_floor ($stack->[$fidx])) { # no floor found
752     splice (@$stack, $fidx, 1)
753     unless $self->check_excluded ($stack->[$fidx])
754    
755     }
756    
757     } elsif ($m eq 'walls') {
758     my $widx = stack_find_wall ($stack, 'from_top');
759    
760     while (arch_is_wall ($stack->[$widx])) {
761     splice (@$stack, $widx, 1);
762     $widx = stack_find_wall ($stack, 'from_top')
763     }
764    
765     } elsif ($m eq 'floor') {
766     my $fidx = stack_find_floor ($stack, 'from_top');
767    
768     while (arch_is_floor ($stack->[$fidx])) {
769     splice (@$stack, $fidx, 1);
770     $fidx = stack_find_floor ($stack, 'from_top')
771     }
772    
773     } elsif ($m eq 'match') {
774     my $pick_name = $::MAINWIN->get_pick ()->{_name};
775     my $idx = stack_find ($stack, 'from_top', sub { $_[0]->{_name} eq $pick_name });
776    
777     while ($stack->[$idx] and $stack->[$idx]->{_name} eq $pick_name) {
778     splice (@$stack, $idx, 1);
779     $idx = stack_find ($stack, 'from_top', sub { $_[0]->{_name} eq $pick_name });
780     }
781     }
782     }
783    
784 elmex 1.18 package GCE::EditAction::ConnectExit;
785 elmex 1.16 use Storable qw/dclone/;
786     use GCE::Util;
787     use Gtk2;
788     use File::Spec::Functions;
789     use strict;
790    
791     our @ISA = qw/GCE::EditAction::RadioModed/;
792    
793 elmex 1.18 sub name { 'connectexit' }
794 elmex 1.16
795     sub init {
796     my ($self) = @_;
797    
798     my $vb = new Gtk2::VBox;
799     $self->add_mode_button ($vb, "exit", "exit");
800 elmex 1.37
801 elmex 1.18 $vb->pack_start ($self->{sel_edt} = Gtk2::Entry->new, 0, 1, 0);
802 elmex 1.24 $self->{sel_edt}->set_text (catfile ($::CFG->{MAPDIR}));
803 elmex 1.18
804 elmex 1.16 $vb->pack_start ($self->{sel_lbl} = Gtk2::Label->new, 0, 0, 0);
805     $self->tool_widget ($vb);
806     }
807    
808     sub want_cursor { 0 }
809    
810 elmex 1.37 #XXX: change_begin/end is handled in edit
811     sub begin { }
812     sub end { }
813 elmex 1.16
814     sub edit {
815     my ($self, $map, $x, $y, $mapedit) = @_;
816    
817     my $pick = $::MAINWIN->get_pick;
818     my $as = $map->get ($x, $y);
819    
820     my $exit;
821     for (@$as) {
822 elmex 1.21 if (arch_is_exit ($_)) {
823 elmex 1.16 $exit = $_;
824     }
825     }
826    
827 elmex 1.26 my $mappath = $::CFG->{MAPDIR};
828    
829 elmex 1.16 if ($exit) {
830     if ($self->{sel_exit}) {
831    
832     $exit->{hp} = $self->{sel_exit}->[3];
833     $exit->{sp} = $self->{sel_exit}->[4];
834 elmex 1.26 #$exit->{slaying} = File::Spec->abs2rel ($self->{sel_exit}->[5], $ent);
835 elmex 1.16
836     my $exit2 = $self->{sel_exit}->[0];
837    
838     $exit2->{hp} = $x;
839     $exit2->{sp} = $y;
840    
841 elmex 1.26 my ($m1, $m2) = exit_paths ($mappath, $self->{sel_exit}->[5], $mapedit->{path});
842    
843     $exit2->{slaying} = $m2;
844     $exit->{slaying} = $m1;
845 elmex 1.16
846     $self->SUPER::begin ($map, $x, $y, $mapedit);
847     $map->change_stack ($x, $y, $as);
848     $self->SUPER::end ($map);
849     $self->SUPER::begin ($self->{sel_exit}->[1], $exit->{hp}, $exit->{sp});
850     $self->{sel_exit}->[1]->change_stack ($exit->{hp}, $exit->{sp}, $self->{sel_exit}->[2]);
851     $self->SUPER::end ($self->{sel_exit}->[1]);
852    
853 elmex 1.26 quick_msg ($mapedit, "$exit->{slaying} ($exit->{hp}:$exit->{sp}) $exit->{_name} <=> $exit2->{slaying} ($exit2->{hp}:$exit2->{sp}) $exit2->{_name}", 0);
854 elmex 1.16
855     $::MAINWIN->{edit_collection}{pick}->edit ($map, $x, $y);
856    
857     $self->{sel_exit} = undef;
858     $self->{sel_lbl}->set_text ('');
859     } else {
860     $self->{sel_lbl}->set_text ("src: ($x:$y) $exit->{_name}");
861     $self->{sel_exit} = [$exit, $map, $as, $x, $y, $mapedit->{path}];
862     }
863     } else {
864 elmex 1.18 if ($self->{sel_exit}) {
865    
866     my $exit2 = $self->{sel_exit}->[0];
867    
868     $exit2->{hp} = $x;
869     $exit2->{sp} = $y;
870 elmex 1.26 $exit2->{slaying} = undef;
871 elmex 1.18
872     unless ($exit2->{slaying} =~ m/^\.\./) {
873     $exit2->{slaying} = '/' . $exit2->{slaying};
874     }
875    
876     $self->SUPER::begin ($self->{sel_exit}->[1], $exit->{hp}, $exit->{sp});
877     $self->{sel_exit}->[1]->change_stack ($self->{sel_exit}->[3], $self->{sel_exit}->[4], $self->{sel_exit}->[2]);
878     $self->SUPER::end ($self->{sel_exit}->[1]);
879    
880 elmex 1.26 quick_msg ($mapedit, "($self->{sel_exit}->[3]:$self->{sel_exit}->[4]) $self->{sel_exit}->[0]->{_name} => ($x:$y) $exit2->{_name}", 0);
881 elmex 1.18
882     $::MAINWIN->{edit_collection}{pick}->edit ($map, $x, $y);
883    
884     $self->{sel_exit} = undef;
885     $self->{sel_lbl}->set_text ('');
886     } else {
887     quick_msg ($mapedit, "no exit object found");
888     }
889 elmex 1.16 }
890     }
891    
892 elmex 1.7
893 elmex 1.1 =head1 AUTHOR
894    
895     Marc Lehmann <schmorp@schmorp.de>
896     http://home.schmorp.de/
897    
898     Robin Redeker <elmex@ta-sa.org>
899     http://www.ta-sa.org/
900    
901     =cut
902 root 1.5
903     1
904 elmex 1.1