ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/EditAction.pm
Revision: 1.11
Committed: Wed Mar 15 22:44:04 2006 UTC (18 years, 2 months ago) by elmex
Branch: MAIN
Changes since 1.10: +60 -0 lines
Log Message:
picker now changes the mainwin to place 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 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 $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 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 my ($self, $map, $x, $y) = @_;
49 }
50
51 sub begin {
52 my ($self, $map, $x, $y) = @_;
53
54 $map->change_begin (ref $self);
55 }
56
57 sub end {
58 my ($self, $map) = @_;
59
60 if (my $changeset = $map->change_end) {
61 splice @{ $map->{undo_stack} ||= [] },
62 $map->{undo_stack_pos}++, 1e6,
63 $changeset;
64
65 #TODO: limit undo stack size to some preconfigured limit
66 }
67 }
68
69 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
119 package GCE::EditAction::Pick;
120 use strict;
121
122 our @ISA = qw/GCE::EditAction/;
123
124 sub name { 'pick' }
125
126 sub want_cursor { 0 }
127
128 sub special_arrow { 'GDK_HAND2' }
129
130 sub begin {
131 my ($self, $map, $x, $y) = @_;
132
133 $self->SUPER::begin ($map, $x, $y);
134 $self->edit ($map, $x, $y);
135 }
136
137 sub edit {
138 my ($self, $map, $x, $y) = @_;
139
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 $x = $arch->{virtual_x};
148 $y = $arch->{virtual_y};
149 $arch = $arch->{_virtual};
150 $cstack = $map->get ($x, $y);
151 }
152
153 $::MAINWIN->set_pick ($arch)
154 if @$cstack;
155
156 $::MAINWIN->update_attr_editor ($arch, sub {
157 $map->change_begin (ref $self);
158 $map->change_stack ($x, $y, $cstack);
159 # 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 });
167
168 $::MAINWIN->update_stack_view ($map, $x, $y);
169 }
170
171 package GCE::EditAction::Place;
172
173 use GCE::Util;
174 use Gtk2;
175 use strict;
176
177 our @ISA = qw/GCE::EditAction::RadioModed/;
178
179 sub name { 'place' }
180
181 sub init {
182 my ($self) = @_;
183
184 my $vb = new Gtk2::VBox;
185 $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
191 $self->tool_widget ($vb);
192 }
193
194 sub want_cursor { 0 }
195
196 sub begin {
197 my ($self, $map, $x, $y) = @_;
198
199 $self->SUPER::begin ($map, $x, $y);
200 $self->edit ($map, $x, $y);
201 }
202
203 sub edit {
204 my ($self, $map, $x, $y) = @_;
205
206 my $pick = $::MAINWIN->get_pick;
207 my $as = $map->get ($x, $y);
208
209 my $arch = { _name => $pick->{_name} };
210
211 $self->stack_action ($as, $arch);
212 $map->change_stack ($x, $y, $as); # insert_arch_stack_layer ($as, $arch));
213 }
214
215 sub stack_action {
216 my ($self, $stack, $arch) = @_;
217
218 my $m = $self->get_mode;
219
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 # if we have a wall above the floor, replace it with the to place item
277 $stack->[$widx] = $arch;
278 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 package GCE::EditAction::Selection;
292 use GCE::Util;
293 use Gtk2;
294 use strict;
295
296 our @ISA = qw/GCE::EditAction::RadioModed/;
297
298 sub name { 'place' }
299
300 sub init {
301 my ($self) = @_;
302
303 my $vb = new Gtk2::VBox;
304 $self->add_mode_button ($vb, "place", "place");
305 $self->add_mode_button ($vb, "erase", "erase");
306
307 $vb->pack_start (my $bt = Gtk2::Button->new ("copy"), 0, 1, 0);
308 $bt->signal_connect ();
309
310 $vb->pack_start (my $bt = Gtk2::Button->new ("paste"), 0, 1, 0);
311 $vb->pack_start (my $bt = Gtk2::Button->new ("invoke"), 0, 1, 0);
312
313 $self->tool_widget ($vb);
314 }
315
316 sub want_cursor { 0 }
317
318 sub begin {
319 my ($self, $map, $x, $y) = @_;
320
321 $self->SUPER::begin ($map, $x, $y);
322
323 delete $self->{selection};
324
325 $self->edit ($map, $x, $y);
326
327 $self->{selection}->{a} = [$x, $y];
328 }
329
330 sub edit {
331 my ($self, $map, $x, $y) = @_;
332
333 $self->{selection}->{b} = [$x, $y];
334
335 $self->update_overlay ($map);
336 }
337
338 sub update_overlay {
339 my ($self, $map) = @_;
340
341 $map->overlay (selection => sub {
342 my ($self) = @_;
343 # $self->{window}->draw_rectangle (
344 # $_ & 1 ? $self->style->black_gc : $self->style->white_gc,
345 # 0, $x + $_, $y + $_, TILESIZE - 1 - $_ * 2, TILESIZE - 1 - $_ * 2)
346 # for 0..3;
347
348 });
349 }
350
351 package GCE::EditAction::Erase;
352 use GCE::Util;
353 use Gtk2;
354 use strict;
355
356 our @ISA = qw/GCE::EditAction::RadioModed/;
357
358 sub name { 'erase' }
359
360 sub want_cursor { 0 }
361
362 sub special_arrow { 'GDK_TCROSS' }
363
364 sub init {
365 my ($self) = @_;
366
367 my $vb = new Gtk2::VBox;
368 $self->add_mode_button ($vb, "top", "top");
369 $self->add_mode_button ($vb, "walls", "walls");
370 $self->add_mode_button ($vb, "above floor", "above", 'default');
371 $self->add_mode_button ($vb, "floor", "floor");
372 $self->add_mode_button ($vb, "below floor", "below");
373 $self->add_mode_button ($vb, "bottom", "bottom");
374 $self->add_mode_button ($vb, "pick match", "match");
375
376 $self->tool_widget ($vb);
377
378 $vb->pack_start ($self->{no_wall_check} = Gtk2::CheckButton->new ("exclude walls"), 0, 1, 0);
379 $vb->pack_start ($self->{no_monsters_check} = Gtk2::CheckButton->new ("exclude monsters"), 0, 1, 0);
380 }
381
382 sub check_excluded {
383 my ($self, $arch) = @_;
384
385 my $a1 = $self->{no_monsters_check}->get_active;
386 my $a2 = $self->{no_wall_check}->get_active;
387
388 my $r = ($self->{no_wall_check}->get_active && arch_is_wall ($arch))
389 || ($self->{no_monsters_check}->get_active && arch_is_monster ($arch));
390 return $r;
391 }
392
393 sub begin {
394 my ($self, $map, $x, $y) = @_;
395
396 $self->SUPER::begin ($map, $x, $y);
397 $self->edit ($map, $x, $y);
398 }
399
400 sub edit {
401 my ($self, $map, $x, $y) = @_;
402
403 my $as = $map->get ($x, $y);
404 $self->stack_action ($as);
405 $map->change_stack ($x, $y, $as);
406 }
407
408 sub stack_action {
409 my ($self, $stack) = @_;
410
411 my $m = $self->get_mode;
412
413 if ($m eq 'top') {
414 pop @$stack;
415
416 } elsif ($m eq 'bottom') {
417 shift @$stack;
418
419 } elsif ($m eq 'above') {
420 my $fidx = stack_find_floor ($stack, 'from_top');
421
422 if (arch_is_floor ($stack->[$fidx]) and $stack->[$fidx + 1]) {
423 splice (@$stack, $fidx + 1, 1)
424 unless $self->check_excluded ($stack->[$fidx + 1])
425
426 } elsif (not arch_is_floor ($stack->[$fidx])) {
427 splice (@$stack, $fidx, 1)
428 unless $self->check_excluded ($stack->[$fidx])
429
430 }
431
432 } elsif ($m eq 'below') {
433 my $fidx = stack_find_floor ($stack, 'from_bottom');
434
435 if ($fidx > 0 and not arch_is_floor ($stack->[$fidx - 1])) {
436 splice (@$stack, $fidx - 1, 1)
437 unless $self->check_excluded ($stack->[$fidx - 1])
438
439 } elsif (not arch_is_floor ($stack->[$fidx])) { # no floor found
440 splice (@$stack, $fidx, 1)
441 unless $self->check_excluded ($stack->[$fidx])
442
443 }
444
445 } elsif ($m eq 'walls') {
446 my $widx = stack_find_wall ($stack, 'from_top');
447
448 while (arch_is_wall ($stack->[$widx])) {
449 splice (@$stack, $widx, 1);
450 $widx = stack_find_wall ($stack, 'from_top')
451 }
452
453 } elsif ($m eq 'floor') {
454 my $fidx = stack_find_floor ($stack, 'from_top');
455
456 while (arch_is_floor ($stack->[$fidx])) {
457 splice (@$stack, $fidx, 1);
458 $fidx = stack_find_floor ($stack, 'from_top')
459 }
460
461 } elsif ($m eq 'match') {
462 my $pick_name = $::MAINWIN->get_pick ()->{_name};
463 my $idx = stack_find ($stack, 'from_top', sub { $_[0]->{_name} eq $pick_name });
464
465 while ($stack->[$idx] and $stack->[$idx]->{_name} eq $pick_name) {
466 splice (@$stack, $idx, 1);
467 $idx = stack_find ($stack, 'from_top', sub { $_[0]->{_name} eq $pick_name });
468 }
469 }
470 }
471
472
473
474 =head1 AUTHOR
475
476 Marc Lehmann <schmorp@schmorp.de>
477 http://home.schmorp.de/
478
479 Robin Redeker <elmex@ta-sa.org>
480 http://www.ta-sa.org/
481
482 =cut
483
484 1
485