ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/EditAction.pm
Revision: 1.41
Committed: Mon Jun 5 09:23:29 2006 UTC (18 years ago) by elmex
Branch: MAIN
Changes since 1.40: +1 -5 lines
Log Message:
set default position for the attribute editor and some other enhancements on the
connect tool.

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