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

# Content
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 Deliantra;
14 use Deliantra::MapWidget;
15
16 use strict;
17
18 sub new {
19 my $class = shift;
20 my $self = { @_ };
21 bless $self, $class;
22 $self->init;
23 return $self;
24 }
25
26 sub name { } # a unique name for this tool (for storing it in a hash in the main window)
27
28 sub tool_widget { if ($_[1]) { $_[0]->{widget} = $_[1] } $_[0]->{widget} }
29 sub init { }
30
31 sub want_cursor { 1 }
32
33 sub only_on_click { 0 }
34
35 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 my ($self, $map, $x, $y) = @_;
51 }
52
53 sub begin {
54 my ($self, $map, $x, $y) = @_;
55 #d# warn "CHANGE BEGIN ".(ref $self)."\n";
56 $map->change_begin (ref $self);
57 }
58
59 sub end {
60 my ($self, $map) = @_;
61
62 #d# warn "CHANGE END ".(ref $self)."\n";
63 if (my $changeset = $map->change_end) {
64 splice @{ $map->{undo_stack} ||= [] },
65 $map->{undo_stack_pos}++, 1e6,
66 $changeset;
67
68 #TODO: limit undo stack size to some preconfigured limit
69 }
70 }
71
72 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
122 package GCE::EditAction::Pick;
123 use GCE::ArchRef;
124 use GCE::Util;
125 use strict;
126
127 our @ISA = qw/GCE::EditAction::RadioModed/;
128
129 sub name { 'pick' }
130
131 sub want_cursor { 0 }
132
133 sub only_on_click { 1 }
134
135 sub special_arrow { 'GDK_HAND2' }
136
137 sub init {
138 my ($self) = @_;
139
140 my $vb = new Gtk2::VBox;
141 $self->{widget} = $vb;
142 }
143
144 # Pick does not change the stack
145 sub begin {}
146 sub end {}
147
148 sub edit {
149 my ($self, $map, $x, $y) = @_;
150
151 my $cstack = $map->get ($x, $y);
152 my ($ox, $oy) = ($x, $y);
153
154 return unless @$cstack;
155
156 my $arch = $cstack->[-1];
157 ($x, $y, $arch, $cstack) = devirtualize ($map, $x, $y, $arch, $cstack);
158
159 my $aref =
160 GCE::ArchRef->new (
161 arch => $arch,
162 source => 'map',
163 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 $::MAINWIN->update_stack_view ($map, $ox, $oy);
177 }
178
179 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 sub special_arrow { 'GDK_HEART' }
190
191 sub init {
192 my ($self) = @_;
193
194 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 my $vb = new Gtk2::VBox;
208 $vb->pack_start (my $combo = Gtk2::ComboBox->new_text, 0, 1, 0);
209 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 $vb->pack_start (my $sw = Gtk2::ScrolledWindow->new, 1, 1, 0);
233 $sw->add ($self->{txt} = Gtk2::TextView->new);
234 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
251 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
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 $upd_combo->();
268
269 $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 my ($self, $map, $pick, $as, $x, $y) = @_;
286 my $buf = $self->{txt}->get_buffer;
287 my $code = $buf->get_text ($buf->get_start_iter, $buf->get_end_iter, 0);
288 my $f_idx = stack_find_floor ($as, 'from_top');
289 my $w_idx = stack_find_wall ($as, 'from_top');
290 my $os = [ map { $Deliantra::ARCH{$_->{_name}} } @$as ];
291
292 unless (arch_is_floor ($as->[$f_idx])) { $f_idx = undef; }
293 unless (arch_is_floor ($as->[$w_idx])) { $w_idx = undef; }
294
295 eval $code;
296 if ($@) {
297 quick_msg ("There is an error in your perl code: $@", 1);
298 }
299 return $as;
300 }
301
302 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 my ($self, $map, $x, $y, $mape) = @_;
325
326 my $as = $map->get ($x, $y);
327
328 my $exit;
329 for my $arch (@$as) {
330 my ($rx, $ry, $rarch) = devirtualize ($map, $x, $y, $arch, $as);
331 if (arch_is_exit ($rarch)) {
332 $exit = $rarch;
333 }
334 }
335
336 if ($exit and $exit->{slaying} !~ /^!/) {
337 my $dest = map2abs ($exit->{slaying}, $mape);
338 my $file = $dest;
339 # XXX: Replace with statusbar message
340 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 }
347 $::MAINWIN->open_map_editor ($file);
348 }
349 }
350
351 package GCE::EditAction::Place;
352
353 use Storable qw/dclone/;
354 use GCE::Util;
355 use Gtk2;
356 use strict;
357
358 our @ISA = qw/GCE::EditAction::RadioModed/;
359
360 sub name { 'place' }
361
362 sub init {
363 my ($self) = @_;
364
365 my $vb = new Gtk2::VBox;
366
367 $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
373 $self->tool_widget ($vb);
374 }
375
376 sub want_cursor { 0 }
377
378 # 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 if ($Deliantra::ARCH{$name}) {
422 %$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 sub edit {
435 my ($self, $map, $x, $y) = @_;
436
437 my $pick = $::MAINWIN->get_pick;
438 my $as = $map->get ($x, $y);
439
440 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 }
457
458 sub end {
459 my ($self, $map, $x, $y, $mape) = @_;
460
461 # 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 delete $self->{last_pos};
469
470 # 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 $self->SUPER::end ($map, $x, $y, $mape);
480 }
481
482 sub stack_action {
483 my ($self, $stack, $arch) = @_;
484
485 my $m = $self->get_mode;
486
487 if ($m eq 'top') {
488 push @$stack, $arch;
489
490 } elsif ($m eq 'bottom') {
491 unshift @$stack, $arch;
492
493 } elsif ($m eq 'above') {
494 my $fidx = stack_find_floor ($stack, 'from_top');
495
496 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 }
509
510 } elsif ($m eq 'below') {
511 my $fidx = stack_find_floor ($stack, 'from_bottom');
512
513 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 }
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 } else { # insert around floor
546 splice (@$stack, $fidx + 1, 0, $arch);
547 }
548
549 } else {
550
551 if (arch_is_wall ($stack->[$widx])) {
552 # if we have a wall above the floor, replace it with the to place item
553 $stack->[$widx] = $arch;
554 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
563 } elsif ($stack->[-1]->{_name} eq $arch->{_name}) {
564 $stack->[-1] = $arch;
565 }
566 }
567 }
568 }
569
570 package GCE::EditAction::Select;
571 use GCE::Util;
572 use Gtk2;
573 use Deliantra;
574 use Storable qw/dclone/;
575 use strict;
576
577 our @ISA = qw/GCE::EditAction::RadioModed/;
578
579 sub name { 'select' }
580
581 sub special_arrow { 'GDK_CIRCLE' }
582
583 sub init {
584 my ($self) = @_;
585
586 my $vb = new Gtk2::VBox;
587
588 $vb->pack_start (my $bt = Gtk2::Button->new_with_mnemonic ("_copy"), 0, 1, 0);
589 $bt->signal_connect (clicked => sub { $self->copy });
590 $vb->pack_start ($self->{paste_top} = Gtk2::CheckButton->new ('paste on top'), 0, 1, 0);
591 $vb->pack_start (my $bt = Gtk2::Button->new_with_mnemonic ("paste (_v)"), 0, 1, 0);
592 $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 $self->add_mode_button ($vb, "perl", "perl");
597 $vb->pack_start (my $bt = Gtk2::Button->new_with_mnemonic ("i_nvoke"), 0, 1, 0);
598 $bt->signal_connect (clicked => sub { $self->invoke });
599
600 $self->tool_widget ($vb);
601 }
602
603 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 my ($self, $map, $xp, $yp) = @_;
626
627 return unless $self->{selection}->{a};
628
629 my ($x1, $y1);
630
631 if (defined $xp) {
632 ($x1, $y1) = ($xp, $yp);
633 } else {
634 ($x1, $y1) = @{$self->{selection}->{a}};
635 }
636
637 $map ||= $self->{selection}->{map};
638
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 } elsif ($m eq 'perl') {
682 $::MAINWIN->{edit_collection}{perl}->edit ($map, $x, $y);
683 }
684 }
685 }
686 $self->SUPER::end ($map);
687 }
688
689 sub want_cursor { 0 }
690
691 sub begin {
692 my ($self, $map, $x, $y) = @_;
693
694 if ($self->{selection}->{map}) {
695 $self->{selection}->{map}->overlay ('selection');
696 }
697 delete $self->{selection};
698
699 $self->{selection}->{a} = [$x, $y];
700 }
701
702 sub end {
703 }
704
705 sub edit {
706 my ($self, $map, $x, $y) = @_;
707
708 $self->{selection}->{b} = [$x, $y];
709 $self->{selection}->{map} = $map;
710 $self->update_overlay ();
711 }
712
713 sub update_overlay {
714 my ($self) = @_;
715
716 my $map = $self->{selection}->{map};
717
718 return unless (defined $self->{selection}->{a} and defined $self->{selection}->{b});
719
720 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
726 my $w = ($x2 - $x1) + 1;
727 my $h = ($y2 - $y1) + 1;
728
729 $map->overlay (selection =>
730 $x1 * TILESIZE, $y1 * TILESIZE,
731 $w * TILESIZE, $h * TILESIZE,
732 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 }
744
745 package GCE::EditAction::Erase;
746 use GCE::Util;
747 use Gtk2;
748 use strict;
749
750 our @ISA = qw/GCE::EditAction::RadioModed/;
751
752 sub name { 'erase' }
753
754 sub want_cursor { 0 }
755
756 sub special_arrow { 'GDK_DIAMOND_CROSS' }
757
758 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 $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 }
775
776 sub check_excluded {
777 my ($self, $arch) = @_;
778
779 my $r = ($self->{no_wall_check}->get_active && arch_is_wall ($arch))
780 || ($self->{no_monsters_check}->get_active && arch_is_monster ($arch));
781
782 return $r;
783 }
784
785 sub edit {
786 my ($self, $map, $x, $y) = @_;
787
788 my $as = $map->get ($x, $y);
789 $self->stack_action ($as);
790 $map->change_stack ($x, $y, $as);
791 }
792
793 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 package GCE::EditAction::Connect;
858 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 sub name { 'connect' }
867
868 sub init {
869 my ($self) = @_;
870
871 my $vb = new Gtk2::VBox;
872 #not supported anymore: $self->add_mode_button ($vb, "auto", "auto", 1);
873 $self->add_mode_button ($vb, "exit", "exit");
874 $self->add_mode_button ($vb, "exit (no path)", "exit_no_slay");
875 $self->add_mode_button ($vb, "connect", "connect");
876
877 $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 $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 #XXX: change_begin/end is handled in edit
887 sub begin { }
888 sub end { }
889
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 my $mode = $self->get_mode;
897
898 my $exit;
899 my $conns = [];
900 for (@$as) {
901 if ($_->{_virtual}) {
902 # FIXME: implement virtual handling for connect, for now ignore
903 next;
904 }
905 if (arch_is_connector ($_)) {
906 push @$conns, $_;
907 }
908 if (arch_is_exit ($_)) {
909 $exit = $_;
910 }
911 }
912
913 if ($mode =~ /^exit/) {
914 $conns = [];
915
916 if ($self->{sel_pos}) {
917 my $pos = delete $self->{sel_pos};
918
919 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
939 $self->SUPER::begin ($map, $x, $y, $mapedit);
940 $map->change_stack ($x, $y, $as);
941 $self->SUPER::end ($map);
942 $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
946 #bothers schmorp: quick_msg ($mapedit, "$exit->{slaying} ($exit->{hp}:$exit->{sp}) $exit->{_name} <=> $exit2->{slaying} ($exit2->{hp}:$exit2->{sp}) $exit2->{_name}", 0);
947
948 $::MAINWIN->{edit_collection}{pick}->edit ($map, $x, $y);
949
950 delete $self->{sel_pos};
951 $self->{sel_lbl}->set_text ('');
952 } else {
953 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 }
964 } elsif ($mode eq 'connect') {
965 for (@$conns) {
966 $_->{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 }
972 }
973
974 =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
984 1
985