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