ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/EditAction.pm
Revision: 1.13
Committed: Thu Mar 16 00:12:06 2006 UTC (18 years, 2 months ago) by elmex
Branch: MAIN
Changes since 1.12: +48 -0 lines
Log Message:
added perl eval tool and removed debugging output

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
54     $map->change_begin (ref $self);
55 elmex 1.1 }
56 root 1.3
57 elmex 1.1 sub end {
58 root 1.3 my ($self, $map) = @_;
59    
60     if (my $changeset = $map->change_end) {
61     splice @{ $map->{undo_stack} ||= [] },
62 root 1.4 $map->{undo_stack_pos}++, 1e6,
63 root 1.3 $changeset;
64    
65     #TODO: limit undo stack size to some preconfigured limit
66     }
67 elmex 1.1 }
68    
69 elmex 1.7 package GCE::EditAction::RadioModed;
70    
71     our @ISA = qw/GCE::EditAction/;
72    
73     sub add_mode_button {
74     my ($self, $vb, $lbl, $mode, $default) = @_;
75    
76     $vb->pack_start (my $b = Gtk2::RadioButton->new ($self->{place_radio_grp}, $lbl), 0, 1, 0);
77     unless (defined $self->{place_radio_grp}) {
78     $self->{place_radio_grp} = $b->get_group;
79    
80     unless (defined $default) {
81     $b->set_active (1);
82     $self->set_mode ($mode);
83     }
84     }
85     if ($default) {
86     $b->set_active (1);
87     $self->set_mode ($mode);
88     }
89     $b->signal_connect (clicked => sub {
90     $self->set_mode ($mode);
91     });
92     }
93    
94     sub set_mode {
95     my ($self, $mode) = @_;
96     $self->{place_mode} = $mode;
97     }
98    
99     sub get_mode {
100     my ($self) = @_;
101     $self->{place_mode}
102     }
103    
104     sub init {
105     my ($self) = @_;
106    
107     die "Implement me!!";
108    
109     # my $vb = new Gtk2::VBox;
110     # $self->_add_mode_button ($vb, "auto", "auto");
111     # $self->_add_mode_button ($vb, "top", "top");
112     # $self->_add_mode_button ($vb, "above floor", "above");
113     # $self->_add_mode_button ($vb, "below floor", "below");
114     # $self->_add_mode_button ($vb, "bottom", "bottom");
115     #
116     # $self->{widget} = $vb;
117     }
118 elmex 1.1
119     package GCE::EditAction::Pick;
120 elmex 1.7 use strict;
121 elmex 1.1
122     our @ISA = qw/GCE::EditAction/;
123    
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     sub begin {
131 elmex 1.10 my ($self, $map, $x, $y) = @_;
132 elmex 1.1
133 elmex 1.10 $self->SUPER::begin ($map, $x, $y);
134     $self->edit ($map, $x, $y);
135 elmex 1.1 }
136    
137     sub edit {
138 elmex 1.10 my ($self, $map, $x, $y) = @_;
139 elmex 1.1
140     my $cstack = $map->get ($x, $y);
141    
142     my $arch = $cstack->[-1];
143    
144     # virtual... grmbl....
145     # FIXME: I have to patch the stack of the real arch??? argl.. how??
146     if ($arch->{_virtual}) {
147 root 1.3 $x = $arch->{virtual_x};
148     $y = $arch->{virtual_y};
149 elmex 1.1 $arch = $arch->{_virtual};
150 elmex 1.6 $cstack = $map->get ($x, $y);
151 elmex 1.1 }
152    
153 root 1.5 $::MAINWIN->set_pick ($arch)
154 elmex 1.10 if @$cstack;
155 elmex 1.1
156 root 1.5 $::MAINWIN->update_attr_editor ($arch, sub {
157 elmex 1.8 $map->change_begin (ref $self);
158 root 1.3 $map->change_stack ($x, $y, $cstack);
159 elmex 1.8 # XXX: Put this into a generic function!!! See also EditTools.pm
160     # FIXME: Fix the automatic update on undo here!
161     if (my $changeset = $map->change_end) {
162     splice @{ $map->{undo_stack} ||= [] },
163     $map->{undo_stack_pos}++, 1e6,
164     $changeset;
165     }
166 elmex 1.1 });
167    
168 root 1.5 $::MAINWIN->update_stack_view ($map, $x, $y);
169 elmex 1.1 }
170    
171 elmex 1.13 package GCE::EditAction::Perl;
172    
173     use GCE::Util;
174     use Gtk2;
175     use strict;
176    
177     our @ISA = qw/GCE::EditAction/;
178    
179     sub name { 'perl' }
180    
181     sub init {
182     my ($self) = @_;
183    
184     my $vb = new Gtk2::VBox;
185     $vb->pack_start (my $sw = Gtk2::ScrolledWindow->new, 1, 1, 0);
186     $sw->add ($self->{txt} = Gtk2::TextView->new);
187    
188     $self->tool_widget ($vb);
189     }
190    
191     sub want_cursor { 0 }
192    
193     sub begin {
194     my ($self, $map, $x, $y) = @_;
195    
196     $self->SUPER::begin ($map, $x, $y);
197     $self->edit ($map, $x, $y);
198     }
199    
200     sub edit {
201     my ($self, $map, $x, $y) = @_;
202    
203     my $pick = $::MAINWIN->get_pick;
204     my $as = $map->get ($x, $y);
205    
206     $as = $self->eval ($map, $pick, $as, $x, $y);
207     $map->change_stack ($x, $y, $as); # insert_arch_stack_layer ($as, $arch));
208     }
209    
210     sub eval {
211     my ($self, $map, $pick, $stack, $x, $y) = @_;
212     my $buf = $self->{txt}->get_buffer;
213     my $code = $buf->get_text ($buf->get_start_iter, $buf->get_end_iter, 0);
214    
215     eval $code;
216     return $stack;
217     }
218    
219 elmex 1.1 package GCE::EditAction::Place;
220    
221 elmex 1.2 use GCE::Util;
222 elmex 1.6 use Gtk2;
223     use strict;
224 elmex 1.2
225 elmex 1.7 our @ISA = qw/GCE::EditAction::RadioModed/;
226 elmex 1.6
227 elmex 1.10 sub name { 'place' }
228    
229 elmex 1.6 sub init {
230     my ($self) = @_;
231    
232     my $vb = new Gtk2::VBox;
233 elmex 1.7 $self->add_mode_button ($vb, "auto", "auto");
234     $self->add_mode_button ($vb, "top", "top");
235     $self->add_mode_button ($vb, "above floor", "above");
236     $self->add_mode_button ($vb, "below floor", "below");
237     $self->add_mode_button ($vb, "bottom", "bottom");
238 elmex 1.6
239 elmex 1.7 $self->tool_widget ($vb);
240 elmex 1.6 }
241    
242 elmex 1.1 sub want_cursor { 0 }
243    
244     sub begin {
245 elmex 1.10 my ($self, $map, $x, $y) = @_;
246 elmex 1.1
247 elmex 1.10 $self->SUPER::begin ($map, $x, $y);
248     $self->edit ($map, $x, $y);
249 elmex 1.1 }
250    
251     sub edit {
252 elmex 1.10 my ($self, $map, $x, $y) = @_;
253 elmex 1.1
254 elmex 1.10 my $pick = $::MAINWIN->get_pick;
255     my $as = $map->get ($x, $y);
256 elmex 1.1
257 elmex 1.10 my $arch = { _name => $pick->{_name} };
258 elmex 1.1
259 elmex 1.10 $self->stack_action ($as, $arch);
260     $map->change_stack ($x, $y, $as); # insert_arch_stack_layer ($as, $arch));
261 elmex 1.1 }
262    
263 elmex 1.6 sub stack_action {
264     my ($self, $stack, $arch) = @_;
265    
266 elmex 1.7 my $m = $self->get_mode;
267 elmex 1.6
268     if ($m eq 'top') {
269     if (@$stack == 0 or $stack->[-1]->{_name} ne $arch->{_name}) {
270     push @$stack, $arch;
271     }
272    
273     } elsif ($m eq 'bottom') {
274     if (@$stack == 0 or $stack->[0]->{_name} ne $arch->{_name}) {
275     unshift @$stack, $arch;
276     }
277    
278     } elsif ($m eq 'above') {
279     my $fidx = stack_find_floor ($stack, 'from_top');
280    
281     if (@$stack == 0
282     or not ($stack->[$fidx + 1])
283     or $stack->[$fidx + 1]->{_name} ne $arch->{_name})
284     {
285     splice (@$stack, $fidx + 1, 0, $arch);
286     }
287    
288     } elsif ($m eq 'below') {
289     my $fidx = stack_find_floor ($stack, 'from_bottom');
290    
291     if (@$stack == 0
292     or $fidx == 0
293     or not ($stack->[$fidx - 1])
294     or $stack->[$fidx - 1]->{_name} ne $arch->{_name})
295     {
296     splice (@$stack, $fidx, 0, $arch);
297     }
298    
299     } elsif ($m eq 'auto') {
300     my $fidx = stack_find_floor ($stack, 'from_top');
301     my $widx = stack_find_wall ($stack);
302    
303     if (arch_is_floor ($arch)) { # we want to place a floor tile
304    
305     if (arch_is_floor ($stack->[$fidx])) { # replace
306     $stack->[$fidx] = $arch;
307    
308     } else { # insert on bottom
309     unshift @$stack, $arch;
310     }
311    
312     } elsif (arch_is_wall ($arch)) { # we want to place a wall
313    
314     if (arch_is_wall ($stack->[$widx])) { # replace
315     $stack->[$widx] = $arch;
316    
317     } else { # insert above floor
318     splice (@$stack, $fidx + 1, 0, $arch);
319     }
320    
321     } else {
322    
323     if (arch_is_wall ($stack->[$widx])) {
324 elmex 1.9 # if we have a wall above the floor, replace it with the to place item
325     $stack->[$widx] = $arch;
326 elmex 1.6 return;
327     }
328    
329     if (@$stack == 0
330     or not ($stack->[-1])
331     or $stack->[-1]->{_name} ne $arch->{_name})
332     {
333     push @$stack, $arch;
334     }
335     }
336     }
337     }
338    
339 elmex 1.12 package GCE::EditAction::Select;
340 elmex 1.11 use GCE::Util;
341     use Gtk2;
342 elmex 1.12 use Crossfire;
343     use Storable qw/dclone/;
344 elmex 1.11 use strict;
345    
346     our @ISA = qw/GCE::EditAction::RadioModed/;
347    
348 elmex 1.12 sub name { 'select' }
349 elmex 1.11
350     sub init {
351     my ($self) = @_;
352    
353     my $vb = new Gtk2::VBox;
354    
355     $vb->pack_start (my $bt = Gtk2::Button->new ("copy"), 0, 1, 0);
356 elmex 1.12 $bt->signal_connect (clicked => sub { $self->copy });
357     $vb->pack_start ($self->{paste_top} = Gtk2::CheckButton->new ('paste on top'), 0, 1, 0);
358 elmex 1.11 $vb->pack_start (my $bt = Gtk2::Button->new ("paste"), 0, 1, 0);
359 elmex 1.12 $bt->signal_connect (clicked => sub { $self->paste });
360     $vb->pack_start (Gtk2::HSeparator->new, 0, 1, 0);
361     $self->add_mode_button ($vb, "place", "place");
362     $self->add_mode_button ($vb, "erase", "erase");
363 elmex 1.11 $vb->pack_start (my $bt = Gtk2::Button->new ("invoke"), 0, 1, 0);
364 elmex 1.12 $bt->signal_connect (clicked => sub { $self->invoke });
365 elmex 1.11
366     $self->tool_widget ($vb);
367     }
368    
369 elmex 1.12 sub copy {
370     my ($self) = @_;
371    
372     return unless $self->{selection}->{a};
373     my ($x1, $y1) = @{$self->{selection}->{a}};
374     my ($x2, $y2) = @{$self->{selection}->{b}};
375    
376     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
377     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
378    
379     my $map = $self->{selection}->{map};
380    
381     $self->{copy_coords} = [$x1, $y1, $x2, $y2];
382     $self->{copy};
383     for (my $x = $x1; $x <= $x2; $x++) {
384     for (my $y = $y1; $y <= $y2; $y++) {
385     $self->{copy}->[$x - $x1]->[$y - $y1] = $map->get ($x, $y);
386     }
387     }
388     }
389    
390     sub paste {
391     my ($self, $xp, $yp) = @_;
392    
393     return unless $self->{selection}->{a};
394    
395     my ($x1, $y1);
396    
397     if ($xp) {
398     ($x1, $y1) = ($xp, $yp);
399     } else {
400     ($x1, $y1) = @{$self->{selection}->{a}};
401     }
402    
403     my $map = $self->{selection}->{map};
404    
405     my $p_o_top = $self->{paste_top}->get_active * 1;
406    
407     my $w = $self->{copy_coords}->[2] - $self->{copy_coords}->[0];
408     my $h = $self->{copy_coords}->[3] - $self->{copy_coords}->[1];
409     $self->{copy};
410     $self->SUPER::begin ($map, $x1, $y1);
411     for (my $x = $x1; $x <= ($x1 + $w); $x++) {
412     for (my $y = $y1; $y <= ($y1 + $h); $y++) {
413     my $cstck = $map->get ($x, $y);
414    
415     if ($p_o_top) {
416     push @$cstck, @{dclone ($self->{copy}->[$x - $x1]->[$y - $y1] || [])};
417     $map->change_stack ($x, $y, $cstck);
418     } else {
419     $map->change_stack ($x, $y, dclone ($self->{copy}->[$x - $x1]->[$y - $y1] || []));
420     }
421     }
422     }
423     $self->SUPER::end ($map);
424     $map->invalidate_all;
425     }
426    
427     sub invoke {
428     my ($self) = @_;
429    
430     return unless $self->{selection}->{a};
431     my ($x1, $y1) = @{$self->{selection}->{a}};
432     my ($x2, $y2) = @{$self->{selection}->{b}};
433    
434     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
435     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
436    
437     my $map = $self->{selection}->{map};
438    
439     my $m = $self->get_mode;
440     $self->SUPER::begin ($map, $x1, $y1);
441     for (my $x = $x1; $x <= $x2; $x++) {
442     for (my $y = $y1; $y <= $y2; $y++) {
443     if ($m eq 'place') {
444     $::MAINWIN->{edit_collection}{place}->edit ($map, $x, $y);
445     } elsif ($m eq 'erase') {
446     $::MAINWIN->{edit_collection}{erase}->edit ($map, $x, $y);
447     }
448     }
449     }
450     $self->SUPER::end ($map);
451     }
452    
453 elmex 1.11 sub want_cursor { 0 }
454    
455     sub begin {
456     my ($self, $map, $x, $y) = @_;
457    
458     delete $self->{selection};
459    
460 elmex 1.12 $self->{selection}->{a} = [$x, $y];
461 elmex 1.11 $self->edit ($map, $x, $y);
462 elmex 1.12 }
463 elmex 1.11
464 elmex 1.12 sub end {
465 elmex 1.11 }
466    
467     sub edit {
468     my ($self, $map, $x, $y) = @_;
469    
470     $self->{selection}->{b} = [$x, $y];
471 elmex 1.12 $self->{selection}->{map} = $map;
472 elmex 1.11 $self->update_overlay ($map);
473     }
474    
475     sub update_overlay {
476     my ($self, $map) = @_;
477    
478 elmex 1.12 my ($x1, $y1) = @{$self->{selection}->{a}};
479     my ($x2, $y2) = @{$self->{selection}->{b}};
480    
481     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
482     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
483 elmex 1.11
484 elmex 1.12 my $w = ($x2 - $x1) + 1;
485     my $h = ($y2 - $y1) + 1;
486    
487     $map->overlay (selection =>
488     $x1 * TILESIZE, $y1 * TILESIZE,
489     $w * TILESIZE, $h * TILESIZE,
490     sub {
491     my ($self, $x, $y) = @_;
492     $self->{window}->draw_rectangle (
493     $_ & 1 ? $self->style->black_gc : $self->style->white_gc,
494     0,
495     $x + $_, $y + $_,
496     ($w * TILESIZE) - 1 - $_ * 2,
497     ($h * TILESIZE) - 1 - $_ * 2
498     ) for 0..3;
499     }
500     );
501 elmex 1.11 }
502    
503 elmex 1.1 package GCE::EditAction::Erase;
504 elmex 1.7 use GCE::Util;
505     use Gtk2;
506     use strict;
507 elmex 1.1
508 elmex 1.7 our @ISA = qw/GCE::EditAction::RadioModed/;
509 elmex 1.1
510 elmex 1.10 sub name { 'erase' }
511    
512 elmex 1.1 sub want_cursor { 0 }
513    
514 elmex 1.10 sub special_arrow { 'GDK_TCROSS' }
515    
516 elmex 1.7 sub init {
517     my ($self) = @_;
518    
519     my $vb = new Gtk2::VBox;
520     $self->add_mode_button ($vb, "top", "top");
521     $self->add_mode_button ($vb, "walls", "walls");
522     $self->add_mode_button ($vb, "above floor", "above", 'default');
523     $self->add_mode_button ($vb, "floor", "floor");
524     $self->add_mode_button ($vb, "below floor", "below");
525     $self->add_mode_button ($vb, "bottom", "bottom");
526     $self->add_mode_button ($vb, "pick match", "match");
527    
528     $self->tool_widget ($vb);
529    
530     $vb->pack_start ($self->{no_wall_check} = Gtk2::CheckButton->new ("exclude walls"), 0, 1, 0);
531     $vb->pack_start ($self->{no_monsters_check} = Gtk2::CheckButton->new ("exclude monsters"), 0, 1, 0);
532     }
533    
534     sub check_excluded {
535     my ($self, $arch) = @_;
536    
537     my $a1 = $self->{no_monsters_check}->get_active;
538     my $a2 = $self->{no_wall_check}->get_active;
539    
540     my $r = ($self->{no_wall_check}->get_active && arch_is_wall ($arch))
541     || ($self->{no_monsters_check}->get_active && arch_is_monster ($arch));
542     return $r;
543     }
544    
545 elmex 1.1 sub begin {
546 elmex 1.10 my ($self, $map, $x, $y) = @_;
547 elmex 1.1
548 elmex 1.10 $self->SUPER::begin ($map, $x, $y);
549     $self->edit ($map, $x, $y);
550 elmex 1.1 }
551    
552     sub edit {
553 elmex 1.10 my ($self, $map, $x, $y) = @_;
554 elmex 1.1
555     my $as = $map->get ($x, $y);
556 elmex 1.7 $self->stack_action ($as);
557 root 1.3 $map->change_stack ($x, $y, $as);
558 elmex 1.1 }
559    
560 elmex 1.7 sub stack_action {
561     my ($self, $stack) = @_;
562    
563     my $m = $self->get_mode;
564    
565     if ($m eq 'top') {
566     pop @$stack;
567    
568     } elsif ($m eq 'bottom') {
569     shift @$stack;
570    
571     } elsif ($m eq 'above') {
572     my $fidx = stack_find_floor ($stack, 'from_top');
573    
574     if (arch_is_floor ($stack->[$fidx]) and $stack->[$fidx + 1]) {
575     splice (@$stack, $fidx + 1, 1)
576     unless $self->check_excluded ($stack->[$fidx + 1])
577    
578     } elsif (not arch_is_floor ($stack->[$fidx])) {
579     splice (@$stack, $fidx, 1)
580     unless $self->check_excluded ($stack->[$fidx])
581    
582     }
583    
584     } elsif ($m eq 'below') {
585     my $fidx = stack_find_floor ($stack, 'from_bottom');
586    
587     if ($fidx > 0 and not arch_is_floor ($stack->[$fidx - 1])) {
588     splice (@$stack, $fidx - 1, 1)
589     unless $self->check_excluded ($stack->[$fidx - 1])
590    
591     } elsif (not arch_is_floor ($stack->[$fidx])) { # no floor found
592     splice (@$stack, $fidx, 1)
593     unless $self->check_excluded ($stack->[$fidx])
594    
595     }
596    
597     } elsif ($m eq 'walls') {
598     my $widx = stack_find_wall ($stack, 'from_top');
599    
600     while (arch_is_wall ($stack->[$widx])) {
601     splice (@$stack, $widx, 1);
602     $widx = stack_find_wall ($stack, 'from_top')
603     }
604    
605     } elsif ($m eq 'floor') {
606     my $fidx = stack_find_floor ($stack, 'from_top');
607    
608     while (arch_is_floor ($stack->[$fidx])) {
609     splice (@$stack, $fidx, 1);
610     $fidx = stack_find_floor ($stack, 'from_top')
611     }
612    
613     } elsif ($m eq 'match') {
614     my $pick_name = $::MAINWIN->get_pick ()->{_name};
615     my $idx = stack_find ($stack, 'from_top', sub { $_[0]->{_name} eq $pick_name });
616    
617     while ($stack->[$idx] and $stack->[$idx]->{_name} eq $pick_name) {
618     splice (@$stack, $idx, 1);
619     $idx = stack_find ($stack, 'from_top', sub { $_[0]->{_name} eq $pick_name });
620     }
621     }
622     }
623    
624    
625    
626 elmex 1.1 =head1 AUTHOR
627    
628     Marc Lehmann <schmorp@schmorp.de>
629     http://home.schmorp.de/
630    
631     Robin Redeker <elmex@ta-sa.org>
632     http://www.ta-sa.org/
633    
634     =cut
635 root 1.5
636     1
637 elmex 1.1