ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/EditAction.pm
Revision: 1.12
Committed: Wed Mar 15 23:45:48 2006 UTC (18 years, 2 months ago) by elmex
Branch: MAIN
Changes since 1.11: +121 -17 lines
Log Message:
implemented selection tool

File Contents

# User Rev Content
1 elmex 1.1 package GCE::EditAction;
2    
3     =head1 NAME
4    
5     GCE::EditActions - this is the abstraction of edit actions (placing, deleting, etc.)
6    
7     =cut
8    
9     use Gtk2;
10     use Gtk2::Gdk::Keysyms;
11     use Gtk2::SimpleMenu;
12    
13     use Crossfire;
14     use Crossfire::MapWidget;
15    
16     use strict;
17    
18     sub new {
19     my $class = shift;
20     my $self = { @_ };
21     bless $self, $class;
22 elmex 1.6 $self->init;
23 elmex 1.1 return $self;
24     }
25    
26 elmex 1.10 sub name { } # a unique name for this tool (for storing it in a hash in the main window)
27    
28 elmex 1.7 sub tool_widget { if ($_[1]) { $_[0]->{widget} = $_[1] } $_[0]->{widget} }
29 elmex 1.6 sub init { }
30    
31 elmex 1.1 sub want_cursor { 1 }
32    
33     sub special_arrow { }
34    
35     # edits one tile of the map
36     sub edit_one {
37     my ($self, $x, $y) = @_;
38     # do one edition
39     }
40    
41     # edits a selection
42     sub edit_selection {
43     }
44    
45     # abstraction for edit_one and edit_selection ?
46     # takes selection if present
47     sub edit {
48 elmex 1.10 my ($self, $map, $x, $y) = @_;
49 elmex 1.1 }
50    
51     sub begin {
52 elmex 1.10 my ($self, $map, $x, $y) = @_;
53 root 1.3
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     package GCE::EditAction::Place;
172    
173 elmex 1.2 use GCE::Util;
174 elmex 1.6 use Gtk2;
175     use strict;
176 elmex 1.2
177 elmex 1.7 our @ISA = qw/GCE::EditAction::RadioModed/;
178 elmex 1.6
179 elmex 1.10 sub name { 'place' }
180    
181 elmex 1.6 sub init {
182     my ($self) = @_;
183    
184     my $vb = new Gtk2::VBox;
185 elmex 1.7 $self->add_mode_button ($vb, "auto", "auto");
186     $self->add_mode_button ($vb, "top", "top");
187     $self->add_mode_button ($vb, "above floor", "above");
188     $self->add_mode_button ($vb, "below floor", "below");
189     $self->add_mode_button ($vb, "bottom", "bottom");
190 elmex 1.6
191 elmex 1.7 $self->tool_widget ($vb);
192 elmex 1.6 }
193    
194 elmex 1.1 sub want_cursor { 0 }
195    
196     sub begin {
197 elmex 1.10 my ($self, $map, $x, $y) = @_;
198 elmex 1.1
199 elmex 1.10 $self->SUPER::begin ($map, $x, $y);
200     $self->edit ($map, $x, $y);
201 elmex 1.1 }
202    
203     sub edit {
204 elmex 1.10 my ($self, $map, $x, $y) = @_;
205 elmex 1.1
206 elmex 1.10 my $pick = $::MAINWIN->get_pick;
207     my $as = $map->get ($x, $y);
208 elmex 1.1
209 elmex 1.10 my $arch = { _name => $pick->{_name} };
210 elmex 1.1
211 elmex 1.10 $self->stack_action ($as, $arch);
212     $map->change_stack ($x, $y, $as); # insert_arch_stack_layer ($as, $arch));
213 elmex 1.1 }
214    
215 elmex 1.6 sub stack_action {
216     my ($self, $stack, $arch) = @_;
217    
218 elmex 1.7 my $m = $self->get_mode;
219 elmex 1.6
220     if ($m eq 'top') {
221     if (@$stack == 0 or $stack->[-1]->{_name} ne $arch->{_name}) {
222     push @$stack, $arch;
223     }
224    
225     } elsif ($m eq 'bottom') {
226     if (@$stack == 0 or $stack->[0]->{_name} ne $arch->{_name}) {
227     unshift @$stack, $arch;
228     }
229    
230     } elsif ($m eq 'above') {
231     my $fidx = stack_find_floor ($stack, 'from_top');
232    
233     if (@$stack == 0
234     or not ($stack->[$fidx + 1])
235     or $stack->[$fidx + 1]->{_name} ne $arch->{_name})
236     {
237     splice (@$stack, $fidx + 1, 0, $arch);
238     }
239    
240     } elsif ($m eq 'below') {
241     my $fidx = stack_find_floor ($stack, 'from_bottom');
242    
243     if (@$stack == 0
244     or $fidx == 0
245     or not ($stack->[$fidx - 1])
246     or $stack->[$fidx - 1]->{_name} ne $arch->{_name})
247     {
248     splice (@$stack, $fidx, 0, $arch);
249     }
250    
251     } elsif ($m eq 'auto') {
252     my $fidx = stack_find_floor ($stack, 'from_top');
253     my $widx = stack_find_wall ($stack);
254    
255     if (arch_is_floor ($arch)) { # we want to place a floor tile
256    
257     if (arch_is_floor ($stack->[$fidx])) { # replace
258     $stack->[$fidx] = $arch;
259    
260     } else { # insert on bottom
261     unshift @$stack, $arch;
262     }
263    
264     } elsif (arch_is_wall ($arch)) { # we want to place a wall
265    
266     if (arch_is_wall ($stack->[$widx])) { # replace
267     $stack->[$widx] = $arch;
268    
269     } else { # insert above floor
270     splice (@$stack, $fidx + 1, 0, $arch);
271     }
272    
273     } else {
274    
275     if (arch_is_wall ($stack->[$widx])) {
276 elmex 1.9 # if we have a wall above the floor, replace it with the to place item
277     $stack->[$widx] = $arch;
278 elmex 1.6 return;
279     }
280    
281     if (@$stack == 0
282     or not ($stack->[-1])
283     or $stack->[-1]->{_name} ne $arch->{_name})
284     {
285     push @$stack, $arch;
286     }
287     }
288     }
289     }
290    
291 elmex 1.12 package GCE::EditAction::Select;
292 elmex 1.11 use GCE::Util;
293     use Gtk2;
294 elmex 1.12 use Crossfire;
295     use Storable qw/dclone/;
296 elmex 1.11 use strict;
297    
298     our @ISA = qw/GCE::EditAction::RadioModed/;
299    
300 elmex 1.12 sub name { 'select' }
301 elmex 1.11
302     sub init {
303     my ($self) = @_;
304    
305     my $vb = new Gtk2::VBox;
306    
307     $vb->pack_start (my $bt = Gtk2::Button->new ("copy"), 0, 1, 0);
308 elmex 1.12 $bt->signal_connect (clicked => sub { $self->copy });
309     $vb->pack_start ($self->{paste_top} = Gtk2::CheckButton->new ('paste on top'), 0, 1, 0);
310 elmex 1.11 $vb->pack_start (my $bt = Gtk2::Button->new ("paste"), 0, 1, 0);
311 elmex 1.12 $bt->signal_connect (clicked => sub { $self->paste });
312     $vb->pack_start (Gtk2::HSeparator->new, 0, 1, 0);
313     $self->add_mode_button ($vb, "place", "place");
314     $self->add_mode_button ($vb, "erase", "erase");
315 elmex 1.11 $vb->pack_start (my $bt = Gtk2::Button->new ("invoke"), 0, 1, 0);
316 elmex 1.12 $bt->signal_connect (clicked => sub { $self->invoke });
317 elmex 1.11
318     $self->tool_widget ($vb);
319     }
320    
321 elmex 1.12 sub copy {
322     my ($self) = @_;
323    
324     return unless $self->{selection}->{a};
325     my ($x1, $y1) = @{$self->{selection}->{a}};
326     my ($x2, $y2) = @{$self->{selection}->{b}};
327    
328     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
329     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
330    
331     my $map = $self->{selection}->{map};
332    
333     $self->{copy_coords} = [$x1, $y1, $x2, $y2];
334     $self->{copy};
335     for (my $x = $x1; $x <= $x2; $x++) {
336     for (my $y = $y1; $y <= $y2; $y++) {
337     $self->{copy}->[$x - $x1]->[$y - $y1] = $map->get ($x, $y);
338     }
339     }
340     }
341    
342     sub paste {
343     my ($self, $xp, $yp) = @_;
344    
345     return unless $self->{selection}->{a};
346    
347     my ($x1, $y1);
348    
349     if ($xp) {
350     ($x1, $y1) = ($xp, $yp);
351     } else {
352     ($x1, $y1) = @{$self->{selection}->{a}};
353     }
354    
355     my $map = $self->{selection}->{map};
356    
357     my $p_o_top = $self->{paste_top}->get_active * 1;
358    
359     my $w = $self->{copy_coords}->[2] - $self->{copy_coords}->[0];
360     my $h = $self->{copy_coords}->[3] - $self->{copy_coords}->[1];
361     $self->{copy};
362     $self->SUPER::begin ($map, $x1, $y1);
363     for (my $x = $x1; $x <= ($x1 + $w); $x++) {
364     for (my $y = $y1; $y <= ($y1 + $h); $y++) {
365     my $cstck = $map->get ($x, $y);
366    
367     if ($p_o_top) {
368     push @$cstck, @{dclone ($self->{copy}->[$x - $x1]->[$y - $y1] || [])};
369     $map->change_stack ($x, $y, $cstck);
370     } else {
371     $map->change_stack ($x, $y, dclone ($self->{copy}->[$x - $x1]->[$y - $y1] || []));
372     }
373     }
374     }
375     $self->SUPER::end ($map);
376     $map->invalidate_all;
377     }
378    
379     sub invoke {
380     my ($self) = @_;
381    
382     return unless $self->{selection}->{a};
383     my ($x1, $y1) = @{$self->{selection}->{a}};
384     my ($x2, $y2) = @{$self->{selection}->{b}};
385    
386     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
387     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
388    
389     my $map = $self->{selection}->{map};
390    
391     my $m = $self->get_mode;
392     $self->SUPER::begin ($map, $x1, $y1);
393     for (my $x = $x1; $x <= $x2; $x++) {
394     for (my $y = $y1; $y <= $y2; $y++) {
395     if ($m eq 'place') {
396     $::MAINWIN->{edit_collection}{place}->edit ($map, $x, $y);
397     } elsif ($m eq 'erase') {
398     $::MAINWIN->{edit_collection}{erase}->edit ($map, $x, $y);
399     }
400     }
401     }
402     $self->SUPER::end ($map);
403     }
404    
405 elmex 1.11 sub want_cursor { 0 }
406    
407     sub begin {
408     my ($self, $map, $x, $y) = @_;
409    
410     delete $self->{selection};
411    
412 elmex 1.12 $self->{selection}->{a} = [$x, $y];
413 elmex 1.11 $self->edit ($map, $x, $y);
414 elmex 1.12 }
415 elmex 1.11
416 elmex 1.12 sub end {
417 elmex 1.11 }
418    
419     sub edit {
420     my ($self, $map, $x, $y) = @_;
421    
422     $self->{selection}->{b} = [$x, $y];
423 elmex 1.12 $self->{selection}->{map} = $map;
424 elmex 1.11 $self->update_overlay ($map);
425     }
426    
427     sub update_overlay {
428     my ($self, $map) = @_;
429    
430 elmex 1.12 my ($x1, $y1) = @{$self->{selection}->{a}};
431     my ($x2, $y2) = @{$self->{selection}->{b}};
432    
433     if ($x1 > $x2) { ($x2, $x1) = ($x1, $x2) }
434     if ($y1 > $y2) { ($y2, $y1) = ($y1, $y2) }
435 elmex 1.11
436 elmex 1.12 my $w = ($x2 - $x1) + 1;
437     my $h = ($y2 - $y1) + 1;
438    
439     $map->overlay (selection =>
440     $x1 * TILESIZE, $y1 * TILESIZE,
441     $w * TILESIZE, $h * TILESIZE,
442     sub {
443     my ($self, $x, $y) = @_;
444     $self->{window}->draw_rectangle (
445     $_ & 1 ? $self->style->black_gc : $self->style->white_gc,
446     0,
447     $x + $_, $y + $_,
448     ($w * TILESIZE) - 1 - $_ * 2,
449     ($h * TILESIZE) - 1 - $_ * 2
450     ) for 0..3;
451     }
452     );
453 elmex 1.11 }
454    
455 elmex 1.1 package GCE::EditAction::Erase;
456 elmex 1.7 use GCE::Util;
457     use Gtk2;
458     use strict;
459 elmex 1.1
460 elmex 1.7 our @ISA = qw/GCE::EditAction::RadioModed/;
461 elmex 1.1
462 elmex 1.10 sub name { 'erase' }
463    
464 elmex 1.1 sub want_cursor { 0 }
465    
466 elmex 1.10 sub special_arrow { 'GDK_TCROSS' }
467    
468 elmex 1.7 sub init {
469     my ($self) = @_;
470    
471     my $vb = new Gtk2::VBox;
472     $self->add_mode_button ($vb, "top", "top");
473     $self->add_mode_button ($vb, "walls", "walls");
474     $self->add_mode_button ($vb, "above floor", "above", 'default');
475     $self->add_mode_button ($vb, "floor", "floor");
476     $self->add_mode_button ($vb, "below floor", "below");
477     $self->add_mode_button ($vb, "bottom", "bottom");
478     $self->add_mode_button ($vb, "pick match", "match");
479    
480     $self->tool_widget ($vb);
481    
482     $vb->pack_start ($self->{no_wall_check} = Gtk2::CheckButton->new ("exclude walls"), 0, 1, 0);
483     $vb->pack_start ($self->{no_monsters_check} = Gtk2::CheckButton->new ("exclude monsters"), 0, 1, 0);
484     }
485    
486     sub check_excluded {
487     my ($self, $arch) = @_;
488    
489     my $a1 = $self->{no_monsters_check}->get_active;
490     my $a2 = $self->{no_wall_check}->get_active;
491    
492     my $r = ($self->{no_wall_check}->get_active && arch_is_wall ($arch))
493     || ($self->{no_monsters_check}->get_active && arch_is_monster ($arch));
494     return $r;
495     }
496    
497 elmex 1.1 sub begin {
498 elmex 1.10 my ($self, $map, $x, $y) = @_;
499 elmex 1.1
500 elmex 1.10 $self->SUPER::begin ($map, $x, $y);
501     $self->edit ($map, $x, $y);
502 elmex 1.1 }
503    
504     sub edit {
505 elmex 1.10 my ($self, $map, $x, $y) = @_;
506 elmex 1.1
507     my $as = $map->get ($x, $y);
508 elmex 1.7 $self->stack_action ($as);
509 root 1.3 $map->change_stack ($x, $y, $as);
510 elmex 1.1 }
511    
512 elmex 1.7 sub stack_action {
513     my ($self, $stack) = @_;
514    
515     my $m = $self->get_mode;
516    
517     if ($m eq 'top') {
518     pop @$stack;
519    
520     } elsif ($m eq 'bottom') {
521     shift @$stack;
522    
523     } elsif ($m eq 'above') {
524     my $fidx = stack_find_floor ($stack, 'from_top');
525    
526     if (arch_is_floor ($stack->[$fidx]) and $stack->[$fidx + 1]) {
527     splice (@$stack, $fidx + 1, 1)
528     unless $self->check_excluded ($stack->[$fidx + 1])
529    
530     } elsif (not arch_is_floor ($stack->[$fidx])) {
531     splice (@$stack, $fidx, 1)
532     unless $self->check_excluded ($stack->[$fidx])
533    
534     }
535    
536     } elsif ($m eq 'below') {
537     my $fidx = stack_find_floor ($stack, 'from_bottom');
538    
539     if ($fidx > 0 and not arch_is_floor ($stack->[$fidx - 1])) {
540     splice (@$stack, $fidx - 1, 1)
541     unless $self->check_excluded ($stack->[$fidx - 1])
542    
543     } elsif (not arch_is_floor ($stack->[$fidx])) { # no floor found
544     splice (@$stack, $fidx, 1)
545     unless $self->check_excluded ($stack->[$fidx])
546    
547     }
548    
549     } elsif ($m eq 'walls') {
550     my $widx = stack_find_wall ($stack, 'from_top');
551    
552     while (arch_is_wall ($stack->[$widx])) {
553     splice (@$stack, $widx, 1);
554     $widx = stack_find_wall ($stack, 'from_top')
555     }
556    
557     } elsif ($m eq 'floor') {
558     my $fidx = stack_find_floor ($stack, 'from_top');
559    
560     while (arch_is_floor ($stack->[$fidx])) {
561     splice (@$stack, $fidx, 1);
562     $fidx = stack_find_floor ($stack, 'from_top')
563     }
564    
565     } elsif ($m eq 'match') {
566     my $pick_name = $::MAINWIN->get_pick ()->{_name};
567     my $idx = stack_find ($stack, 'from_top', sub { $_[0]->{_name} eq $pick_name });
568    
569     while ($stack->[$idx] and $stack->[$idx]->{_name} eq $pick_name) {
570     splice (@$stack, $idx, 1);
571     $idx = stack_find ($stack, 'from_top', sub { $_[0]->{_name} eq $pick_name });
572     }
573     }
574     }
575    
576    
577    
578 elmex 1.1 =head1 AUTHOR
579    
580     Marc Lehmann <schmorp@schmorp.de>
581     http://home.schmorp.de/
582    
583     Robin Redeker <elmex@ta-sa.org>
584     http://www.ta-sa.org/
585    
586     =cut
587 root 1.5
588     1
589 elmex 1.1