ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/EditAction.pm
Revision: 1.14
Committed: Thu Mar 16 01:13:57 2006 UTC (18 years, 2 months ago) by elmex
Branch: MAIN
Changes since 1.13: +2 -4 lines
Log Message:
removed face checking and implemented pick editing

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 elmex 1.14 use Storable qw/dclone/;
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.14 $self->stack_action ($as, dclone ($pick));
258 elmex 1.10 $map->change_stack ($x, $y, $as); # insert_arch_stack_layer ($as, $arch));
259 elmex 1.1 }
260    
261 elmex 1.6 sub stack_action {
262     my ($self, $stack, $arch) = @_;
263    
264 elmex 1.7 my $m = $self->get_mode;
265 elmex 1.6
266     if ($m eq 'top') {
267     if (@$stack == 0 or $stack->[-1]->{_name} ne $arch->{_name}) {
268     push @$stack, $arch;
269     }
270    
271     } elsif ($m eq 'bottom') {
272     if (@$stack == 0 or $stack->[0]->{_name} ne $arch->{_name}) {
273     unshift @$stack, $arch;
274     }
275    
276     } elsif ($m eq 'above') {
277     my $fidx = stack_find_floor ($stack, 'from_top');
278    
279     if (@$stack == 0
280     or not ($stack->[$fidx + 1])
281     or $stack->[$fidx + 1]->{_name} ne $arch->{_name})
282     {
283     splice (@$stack, $fidx + 1, 0, $arch);
284     }
285    
286     } elsif ($m eq 'below') {
287     my $fidx = stack_find_floor ($stack, 'from_bottom');
288    
289     if (@$stack == 0
290     or $fidx == 0
291     or not ($stack->[$fidx - 1])
292     or $stack->[$fidx - 1]->{_name} ne $arch->{_name})
293     {
294     splice (@$stack, $fidx, 0, $arch);
295     }
296    
297     } elsif ($m eq 'auto') {
298     my $fidx = stack_find_floor ($stack, 'from_top');
299     my $widx = stack_find_wall ($stack);
300    
301     if (arch_is_floor ($arch)) { # we want to place a floor tile
302    
303     if (arch_is_floor ($stack->[$fidx])) { # replace
304     $stack->[$fidx] = $arch;
305    
306     } else { # insert on bottom
307     unshift @$stack, $arch;
308     }
309    
310     } elsif (arch_is_wall ($arch)) { # we want to place a wall
311    
312     if (arch_is_wall ($stack->[$widx])) { # replace
313     $stack->[$widx] = $arch;
314    
315     } else { # insert above floor
316     splice (@$stack, $fidx + 1, 0, $arch);
317     }
318    
319     } else {
320    
321     if (arch_is_wall ($stack->[$widx])) {
322 elmex 1.9 # if we have a wall above the floor, replace it with the to place item
323     $stack->[$widx] = $arch;
324 elmex 1.6 return;
325     }
326    
327     if (@$stack == 0
328     or not ($stack->[-1])
329     or $stack->[-1]->{_name} ne $arch->{_name})
330     {
331     push @$stack, $arch;
332     }
333     }
334     }
335     }
336    
337 elmex 1.12 package GCE::EditAction::Select;
338 elmex 1.11 use GCE::Util;
339     use Gtk2;
340 elmex 1.12 use Crossfire;
341     use Storable qw/dclone/;
342 elmex 1.11 use strict;
343    
344     our @ISA = qw/GCE::EditAction::RadioModed/;
345    
346 elmex 1.12 sub name { 'select' }
347 elmex 1.11
348     sub init {
349     my ($self) = @_;
350    
351     my $vb = new Gtk2::VBox;
352    
353     $vb->pack_start (my $bt = Gtk2::Button->new ("copy"), 0, 1, 0);
354 elmex 1.12 $bt->signal_connect (clicked => sub { $self->copy });
355     $vb->pack_start ($self->{paste_top} = Gtk2::CheckButton->new ('paste on top'), 0, 1, 0);
356 elmex 1.11 $vb->pack_start (my $bt = Gtk2::Button->new ("paste"), 0, 1, 0);
357 elmex 1.12 $bt->signal_connect (clicked => sub { $self->paste });
358     $vb->pack_start (Gtk2::HSeparator->new, 0, 1, 0);
359     $self->add_mode_button ($vb, "place", "place");
360     $self->add_mode_button ($vb, "erase", "erase");
361 elmex 1.11 $vb->pack_start (my $bt = Gtk2::Button->new ("invoke"), 0, 1, 0);
362 elmex 1.12 $bt->signal_connect (clicked => sub { $self->invoke });
363 elmex 1.11
364     $self->tool_widget ($vb);
365     }
366    
367 elmex 1.12 sub copy {
368     my ($self) = @_;
369    
370     return unless $self->{selection}->{a};
371     my ($x1, $y1) = @{$self->{selection}->{a}};
372     my ($x2, $y2) = @{$self->{selection}->{b}};
373    
374     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
375     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
376    
377     my $map = $self->{selection}->{map};
378    
379     $self->{copy_coords} = [$x1, $y1, $x2, $y2];
380     $self->{copy};
381     for (my $x = $x1; $x <= $x2; $x++) {
382     for (my $y = $y1; $y <= $y2; $y++) {
383     $self->{copy}->[$x - $x1]->[$y - $y1] = $map->get ($x, $y);
384     }
385     }
386     }
387    
388     sub paste {
389     my ($self, $xp, $yp) = @_;
390    
391     return unless $self->{selection}->{a};
392    
393     my ($x1, $y1);
394    
395     if ($xp) {
396     ($x1, $y1) = ($xp, $yp);
397     } else {
398     ($x1, $y1) = @{$self->{selection}->{a}};
399     }
400    
401     my $map = $self->{selection}->{map};
402    
403     my $p_o_top = $self->{paste_top}->get_active * 1;
404    
405     my $w = $self->{copy_coords}->[2] - $self->{copy_coords}->[0];
406     my $h = $self->{copy_coords}->[3] - $self->{copy_coords}->[1];
407     $self->{copy};
408     $self->SUPER::begin ($map, $x1, $y1);
409     for (my $x = $x1; $x <= ($x1 + $w); $x++) {
410     for (my $y = $y1; $y <= ($y1 + $h); $y++) {
411     my $cstck = $map->get ($x, $y);
412    
413     if ($p_o_top) {
414     push @$cstck, @{dclone ($self->{copy}->[$x - $x1]->[$y - $y1] || [])};
415     $map->change_stack ($x, $y, $cstck);
416     } else {
417     $map->change_stack ($x, $y, dclone ($self->{copy}->[$x - $x1]->[$y - $y1] || []));
418     }
419     }
420     }
421     $self->SUPER::end ($map);
422     $map->invalidate_all;
423     }
424    
425     sub invoke {
426     my ($self) = @_;
427    
428     return unless $self->{selection}->{a};
429     my ($x1, $y1) = @{$self->{selection}->{a}};
430     my ($x2, $y2) = @{$self->{selection}->{b}};
431    
432     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
433     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
434    
435     my $map = $self->{selection}->{map};
436    
437     my $m = $self->get_mode;
438     $self->SUPER::begin ($map, $x1, $y1);
439     for (my $x = $x1; $x <= $x2; $x++) {
440     for (my $y = $y1; $y <= $y2; $y++) {
441     if ($m eq 'place') {
442     $::MAINWIN->{edit_collection}{place}->edit ($map, $x, $y);
443     } elsif ($m eq 'erase') {
444     $::MAINWIN->{edit_collection}{erase}->edit ($map, $x, $y);
445     }
446     }
447     }
448     $self->SUPER::end ($map);
449     }
450    
451 elmex 1.11 sub want_cursor { 0 }
452    
453     sub begin {
454     my ($self, $map, $x, $y) = @_;
455    
456     delete $self->{selection};
457    
458 elmex 1.12 $self->{selection}->{a} = [$x, $y];
459 elmex 1.11 $self->edit ($map, $x, $y);
460 elmex 1.12 }
461 elmex 1.11
462 elmex 1.12 sub end {
463 elmex 1.11 }
464    
465     sub edit {
466     my ($self, $map, $x, $y) = @_;
467    
468     $self->{selection}->{b} = [$x, $y];
469 elmex 1.12 $self->{selection}->{map} = $map;
470 elmex 1.11 $self->update_overlay ($map);
471     }
472    
473     sub update_overlay {
474     my ($self, $map) = @_;
475    
476 elmex 1.12 my ($x1, $y1) = @{$self->{selection}->{a}};
477     my ($x2, $y2) = @{$self->{selection}->{b}};
478    
479     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
480     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
481 elmex 1.11
482 elmex 1.12 my $w = ($x2 - $x1) + 1;
483     my $h = ($y2 - $y1) + 1;
484    
485     $map->overlay (selection =>
486     $x1 * TILESIZE, $y1 * TILESIZE,
487     $w * TILESIZE, $h * TILESIZE,
488     sub {
489     my ($self, $x, $y) = @_;
490     $self->{window}->draw_rectangle (
491     $_ & 1 ? $self->style->black_gc : $self->style->white_gc,
492     0,
493     $x + $_, $y + $_,
494     ($w * TILESIZE) - 1 - $_ * 2,
495     ($h * TILESIZE) - 1 - $_ * 2
496     ) for 0..3;
497     }
498     );
499 elmex 1.11 }
500    
501 elmex 1.1 package GCE::EditAction::Erase;
502 elmex 1.7 use GCE::Util;
503     use Gtk2;
504     use strict;
505 elmex 1.1
506 elmex 1.7 our @ISA = qw/GCE::EditAction::RadioModed/;
507 elmex 1.1
508 elmex 1.10 sub name { 'erase' }
509    
510 elmex 1.1 sub want_cursor { 0 }
511    
512 elmex 1.10 sub special_arrow { 'GDK_TCROSS' }
513    
514 elmex 1.7 sub init {
515     my ($self) = @_;
516    
517     my $vb = new Gtk2::VBox;
518     $self->add_mode_button ($vb, "top", "top");
519     $self->add_mode_button ($vb, "walls", "walls");
520     $self->add_mode_button ($vb, "above floor", "above", 'default');
521     $self->add_mode_button ($vb, "floor", "floor");
522     $self->add_mode_button ($vb, "below floor", "below");
523     $self->add_mode_button ($vb, "bottom", "bottom");
524     $self->add_mode_button ($vb, "pick match", "match");
525    
526     $self->tool_widget ($vb);
527    
528     $vb->pack_start ($self->{no_wall_check} = Gtk2::CheckButton->new ("exclude walls"), 0, 1, 0);
529     $vb->pack_start ($self->{no_monsters_check} = Gtk2::CheckButton->new ("exclude monsters"), 0, 1, 0);
530     }
531    
532     sub check_excluded {
533     my ($self, $arch) = @_;
534    
535     my $a1 = $self->{no_monsters_check}->get_active;
536     my $a2 = $self->{no_wall_check}->get_active;
537    
538     my $r = ($self->{no_wall_check}->get_active && arch_is_wall ($arch))
539     || ($self->{no_monsters_check}->get_active && arch_is_monster ($arch));
540     return $r;
541     }
542    
543 elmex 1.1 sub begin {
544 elmex 1.10 my ($self, $map, $x, $y) = @_;
545 elmex 1.1
546 elmex 1.10 $self->SUPER::begin ($map, $x, $y);
547     $self->edit ($map, $x, $y);
548 elmex 1.1 }
549    
550     sub edit {
551 elmex 1.10 my ($self, $map, $x, $y) = @_;
552 elmex 1.1
553     my $as = $map->get ($x, $y);
554 elmex 1.7 $self->stack_action ($as);
555 root 1.3 $map->change_stack ($x, $y, $as);
556 elmex 1.1 }
557    
558 elmex 1.7 sub stack_action {
559     my ($self, $stack) = @_;
560    
561     my $m = $self->get_mode;
562    
563     if ($m eq 'top') {
564     pop @$stack;
565    
566     } elsif ($m eq 'bottom') {
567     shift @$stack;
568    
569     } elsif ($m eq 'above') {
570     my $fidx = stack_find_floor ($stack, 'from_top');
571    
572     if (arch_is_floor ($stack->[$fidx]) and $stack->[$fidx + 1]) {
573     splice (@$stack, $fidx + 1, 1)
574     unless $self->check_excluded ($stack->[$fidx + 1])
575    
576     } elsif (not arch_is_floor ($stack->[$fidx])) {
577     splice (@$stack, $fidx, 1)
578     unless $self->check_excluded ($stack->[$fidx])
579    
580     }
581    
582     } elsif ($m eq 'below') {
583     my $fidx = stack_find_floor ($stack, 'from_bottom');
584    
585     if ($fidx > 0 and not arch_is_floor ($stack->[$fidx - 1])) {
586     splice (@$stack, $fidx - 1, 1)
587     unless $self->check_excluded ($stack->[$fidx - 1])
588    
589     } elsif (not arch_is_floor ($stack->[$fidx])) { # no floor found
590     splice (@$stack, $fidx, 1)
591     unless $self->check_excluded ($stack->[$fidx])
592    
593     }
594    
595     } elsif ($m eq 'walls') {
596     my $widx = stack_find_wall ($stack, 'from_top');
597    
598     while (arch_is_wall ($stack->[$widx])) {
599     splice (@$stack, $widx, 1);
600     $widx = stack_find_wall ($stack, 'from_top')
601     }
602    
603     } elsif ($m eq 'floor') {
604     my $fidx = stack_find_floor ($stack, 'from_top');
605    
606     while (arch_is_floor ($stack->[$fidx])) {
607     splice (@$stack, $fidx, 1);
608     $fidx = stack_find_floor ($stack, 'from_top')
609     }
610    
611     } elsif ($m eq 'match') {
612     my $pick_name = $::MAINWIN->get_pick ()->{_name};
613     my $idx = stack_find ($stack, 'from_top', sub { $_[0]->{_name} eq $pick_name });
614    
615     while ($stack->[$idx] and $stack->[$idx]->{_name} eq $pick_name) {
616     splice (@$stack, $idx, 1);
617     $idx = stack_find ($stack, 'from_top', sub { $_[0]->{_name} eq $pick_name });
618     }
619     }
620     }
621    
622    
623    
624 elmex 1.1 =head1 AUTHOR
625    
626     Marc Lehmann <schmorp@schmorp.de>
627     http://home.schmorp.de/
628    
629     Robin Redeker <elmex@ta-sa.org>
630     http://www.ta-sa.org/
631    
632     =cut
633 root 1.5
634     1
635 elmex 1.1