ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.40
Committed: Tue Aug 15 17:01:34 2006 UTC (17 years, 9 months ago) by elmex
Branch: MAIN
Changes since 1.39: +2 -0 lines
Log Message:
added safe map checkbox.

File Contents

# Content
1 package GCE::MapEditor;
2
3 =head1 NAME
4
5 GCE::MapEditor - the map editing widget
6
7 =cut
8
9 use Gtk2;
10 use Gtk2::Gdk::Keysyms;
11 use Gtk2::SimpleMenu;
12
13 use Crossfire;
14 use Crossfire::Map;
15 use Crossfire::MapWidget;
16
17 use GCE::AttrEdit;
18 use GCE::Util;
19
20 use Glib::Object::Subclass
21 Gtk2::Window;
22
23 use strict;
24
25 sub do_context_menu {
26 my ($self, $map, $event) = @_;
27
28 my ($x, $y) = $map->coord ($event->x, $event->y);
29
30 my $menu = Gtk2::Menu->new;
31 foreach my $cm (
32 [
33 Follow => sub {
34 $::MAINWIN->{edit_collection}{followexit}->edit ($map, $x, $y, $self)
35 },
36 ]
37 ) {
38 my $item = Gtk2::MenuItem->new ($cm->[0]);
39 $menu->append ($item);
40 $item->show;
41 $item->signal_connect (activate => $cm->[1]);
42 }
43
44 my $inv_item = Gtk2::MenuItem->new ("Add inventory");
45 $inv_item->set_submenu (my $smenu = new Gtk2::Menu);
46 $menu->append ($inv_item);
47
48 for my $sr (reverse $self->get_stack_refs ($map, $x, $y)) {
49 my $item = Gtk2::MenuItem->new ($sr->longname);
50 $smenu->append ($item);
51 $item->show;
52 $item->signal_connect (activate => sub {
53 $sr->add_inv ($::MAINWIN->get_pick);
54 });
55 }
56
57 $inv_item->show;
58 $menu->popup (undef, undef, undef, undef, $event->button, $event->time);
59 }
60
61 sub build_menu {
62 my ($self) = @_;
63
64 my $menu_tree = [
65 _File => {
66 item_type => '<Branch>',
67 children => [
68 "_Save" => {
69 callback => sub { $self->save_map },
70 accelerator => '<ctrl>S'
71 },
72 "Save As" => {
73 callback => sub { $self->save_map_as },
74 },
75 "_Map Properties" => {
76 callback => sub { $self->open_map_prop },
77 accelerator => "<ctrl>P"
78 },
79 "_Map Resize" => {
80 callback => sub { $self->open_resize_map },
81 },
82 "Close" => {
83 callback => sub { $self->destroy },
84 },
85 ]
86 },
87 _Edit => {
88 item_type => '<Branch>',
89 children => [
90 "_Undo" => {
91 callback => sub { $self->undo },
92 accelerator => "<ctrl>Z"
93 },
94 "_Redo" => {
95 callback => sub { $self->redo },
96 accelerator => "<ctrl>Y"
97 },
98 ]
99 },
100 _Go => {
101 item_type => '<Branch>',
102 children => [
103 "_Up" => {
104 callback => sub { $self->follow ('u') },
105 accelerator => "<ctrl>Up"
106 },
107 "_Down" => {
108 callback => sub { $self->follow ('d') },
109 accelerator => "<ctrl>Down"
110 },
111 "_Right" => {
112 callback => sub { $self->follow ('r') },
113 accelerator => "<ctrl>Right"
114 },
115 "_Left" => {
116 callback => sub { $self->follow ('l') },
117 accelerator => "<ctrl>Left"
118 },
119 ]
120 },
121 _Help => {
122 item_type => '<Branch>',
123 children => [
124 _Manual => {
125 callback => sub { $::MAINWIN->show_help_window },
126 accelerator => "<ctrl>H"
127 },
128 ]
129 },
130 ];
131
132 my $men =
133 Gtk2::SimpleMenu->new (
134 menu_tree => $menu_tree,
135 default_callback => \&default_cb,
136 );
137
138 for (
139 [i => 'pick'],
140 [p => 'place'],
141 [e => 'erase'],
142 [s => 'select'],
143 [l => 'eval'],
144 [t => 'connect'],
145 [f => 'followexit']
146 )
147 {
148 my $tool = $_->[1];
149 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
150 sub { $::MAINWIN->set_edit_tool ($tool) });
151 }
152
153 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{'r'}, ['control-mask'], 'visible',
154 sub { $self->redo });
155
156 $self->add_accel_group ($men->{accel_group});
157
158 return $men->{widget};
159 }
160
161 sub set_edit_tool {
162 my ($self, $tool) = @_;
163
164 $self->{etool} = $tool;
165
166 if ($self->ea->special_arrow) {
167 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
168 } else {
169 # FIXME: Get the original cursor and insert it here
170 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
171 }
172 }
173
174 sub ea {
175 my ($self) = @_;
176 $self->{ea_alt} || $self->{etool};
177 }
178
179 sub INIT_INSTANCE {
180 my ($self) = @_;
181
182 $self->set_title ('gce - map editor');
183 $self->add (my $vb = Gtk2::VBox->new);
184
185 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
186
187 $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0);
188
189 $map->signal_connect_after (key_press_event => sub {
190 my ($map, $event) = @_;
191
192 my $kv = $event->keyval;
193
194 my $ret = 0;
195
196 my ($x, $y) = $map->coord ($map->get_pointer);
197 for ([Control_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{erase} }],
198 [Alt_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{pick} }],
199 [c => sub { $::MAINWIN->{edit_collection}{select}->copy }],
200 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
201 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
202 )
203 {
204 my $ed = $_;
205
206 if ($kv == $Gtk2::Gdk::Keysyms{$ed->[0]}) {
207 my $was_in_draw = defined $self->{draw_mode};
208
209 $self->stop_drawmode ($map)
210 if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
211
212 $ed->[1]->();
213 $ret = 1;
214
215 $self->start_drawmode ($map)
216 if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
217 }
218 }
219
220 if ($self->ea->special_arrow) {
221 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
222 } else {
223 # FIXME: Get the original cursor and insert it here
224 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
225 }
226
227 $ret
228 });
229
230 $map->signal_connect_after (key_release_event => sub {
231 my ($map, $event) = @_;
232
233 my $ret = 0;
234
235 if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L}
236 or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L})
237 {
238 my $was_in_draw = defined $self->{draw_mode};
239
240 $self->stop_drawmode ($map)
241 if $was_in_draw;
242
243 delete $self->{ea_alt};
244 $ret = 1;
245
246 $self->start_drawmode ($map)
247 if $was_in_draw;
248 }
249
250 if ($self->ea->special_arrow) {
251 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
252 } else {
253 # FIXME: Get the original cursor and insert it here
254 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
255 }
256
257 $ret
258 });
259 $map->signal_connect_after (button_press_event => sub {
260 my ($map, $event) = @_;
261
262 if ((not $self->{draw_mode}) and $event->button == 1) {
263 my $ea = $self->ea;
264
265 $self->start_drawmode ($map);
266
267 $ea->want_cursor
268 or $map->disable_tooltip;
269
270 return 1;
271 } elsif ($event->button == 3) {
272 $self->do_context_menu ($map, $event);
273 return 1;
274 }
275
276 0
277 });
278
279 $map->signal_connect_after (motion_notify_event => sub {
280 my ($map, $event) = @_;
281
282 $self->{draw_mode}
283 or return;
284
285 my $ea = $self->ea;
286
287 my ($X, $Y) = @{$self->{draw_mode}}[0,1];
288 my ($x, $y) = $map->coord ($map->get_pointer);
289
290 while ($x != $X || $y != $Y) {
291
292 $X++ if $X < $x;
293 $X-- if $X > $x;
294 $Y++ if $Y < $y;
295 $Y-- if $Y > $y;
296
297 unless ($ea->only_on_click) {
298 $ea->edit ($map, $X, $Y, $self)
299 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
300 }
301 }
302
303 @{$self->{draw_mode}}[0,1] = ($X, $Y);
304
305 1
306 });
307
308 $map->signal_connect_after (button_release_event => sub {
309 my ($map, $event) = @_;
310
311 if ($self->{draw_mode} and $event->button == 1) {
312 my $ea = $self->ea;
313
314 $self->stop_drawmode ($map);
315
316 $ea->want_cursor
317 or $map->enable_tooltip;
318
319 return 1;
320 }
321
322 0
323 });
324 }
325
326 sub start_drawmode {
327 my ($self, $map) = @_;
328
329 $self->{draw_mode} and return;
330
331 # XXX: is this okay? my ($x, $y) = $map->coord ($event->x, $event->y);
332 my ($x, $y) = $map->coord ($map->get_pointer);
333
334 my $ea = $self->ea;
335
336 $ea->begin ($map, $x, $y, $self);
337
338 $ea->edit ($map, $x, $y, $self)
339 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
340
341 $self->{draw_mode} = [$x, $y];
342 }
343
344 sub stop_drawmode {
345 my ($self, $map) = @_;
346
347 $self->{draw_mode} or return;
348
349 my ($x, $y) = $map->coord ($map->get_pointer);
350
351 my $ea = $self->ea;
352 $ea->end ($map, $x, $y, $self);
353
354 delete $self->{draw_mode};
355 }
356
357 # FIXME: Fix the automatic update of the attribute editor! and also the stack view!
358 sub undo {
359 my ($self) = @_;
360
361 my $map = $self->{map}; # the Crossfire::MapWidget
362
363 $map->{undo_stack_pos}
364 or return;
365
366 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
367 }
368
369 sub get_stack_refs {
370 my ($self, $map, $x, $y) = @_;
371
372 my $cstack = $map->get ($x, $y);
373
374 return [] unless @$cstack;
375
376 my @refs;
377
378 for my $arch (@$cstack) {
379 my ($ox, $oy) = ($x, $y);
380 if ($arch->{_virtual}) {
381 $ox = $arch->{virtual_x};
382 $oy = $arch->{virtual_y};
383 $arch = $arch->{_virtual};
384 $cstack = $map->get ($ox, $oy);
385 # XXX: This heavily blows up if $arch isn't on $cstack now.. and it actually really does :(
386 }
387
388 push @refs,
389 GCE::ArchRef->new (
390 arch => $arch,
391 cb => sub {
392 $map->change_begin ('attredit');
393 $map->change_stack ($ox, $oy, $cstack);
394
395 if (my $changeset = $map->change_end) {
396 splice @{ $map->{undo_stack} ||= [] },
397 $map->{undo_stack_pos}++, 1e6,
398 $changeset;
399 }
400 }
401 );
402 }
403
404 return @refs;
405 }
406
407 sub redo {
408 my ($self) = @_;
409
410 my $map = $self->{map}; # the Crossfire::MapWidget
411
412 $map->{undo_stack}
413 and $map->{undo_stack_pos} < @{$map->{undo_stack}}
414 or return;
415
416 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
417 }
418
419 sub open_map {
420 my ($self, $path, $key) = @_;
421
422 $self->{mapkey} = $key;
423
424 if (ref $path) {
425 $self->{map}->set_map ($path);
426 $self->set_title ('<ram>');
427
428 } else {
429 $self->{path} = $path;
430 # print "OPENMAP $path\n";
431 $self->{map}->set_map (my $m = new_from_file Crossfire::Map $path);
432 $self->set_title ("gce - map editor - $self->{path}");
433 require Data::Dumper;
434 # print "FOO:" .Data::Dumper::Dumper ($m) . "\n";
435 }
436 }
437
438 sub save_map {
439 my ($self) = @_;
440
441 if ($self->{path}) {
442 $self->{map}{map}->write_file ($self->{path});
443 quick_msg ($self, "saved to $self->{path}");
444 $self->set_title ("gce - map editor - $self->{path}");
445 } else {
446 $self->save_map_as;
447 }
448 }
449
450 sub save_map_as {
451 my ($self) = @_;
452
453 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
454
455 if ('ok' eq $fc->run) {
456
457 $::MAINWIN->{fc_last_folder} = $fc->get_current_folder;
458 $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++;
459
460 $self->{map}{map}->write_file ($self->{path} = $fc->get_filename);
461 quick_msg ($self, "saved to $self->{path}");
462 $self->set_title ("gce - map editor - $self->{path}");
463 }
464
465 $fc->destroy;
466 }
467
468 sub _add_prop_entry {
469 my ($self, $table, $idx, $key, $desc, $type, $changecb) = @_;
470
471 my $edwid;
472
473 if ($type eq 'string') {
474 $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
475 $edwid = Gtk2::Entry->new;
476 $edwid->set_text ($self->{map}{map}{info}{$key});
477 $edwid->signal_connect (changed => sub {
478 $self->{map}{map}{info}{$key} = $_[0]->get_text;
479 if ($changecb) {
480 $changecb->($_[0]->get_text);
481 }
482 });
483 $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
484
485 } elsif ($type eq 'button') {
486 $table->attach_defaults (my $b = Gtk2::Button->new_with_label ($desc), 0, 2, $idx, $idx + 1);
487 $b->signal_connect (clicked => ($changecb || sub {}));
488
489 } elsif ($type eq 'label') {
490 $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
491 $edwid = Gtk2::Label->new ($self->{map}{map}{info}{$key});
492 $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
493
494 } elsif ($type eq 'check') {
495 $table->attach_defaults (my $lbl1 = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
496 $table->attach_defaults (my $lbl = Gtk2::CheckButton->new, 1, 2, $idx, $idx + 1);
497 $lbl->set_active ($self->{map}{map}{info}{$key});
498 $lbl->signal_connect (toggled => sub {
499 my ($lbl) = @_;
500 $self->{map}{map}{info}{$key} = $lbl->get_active * 1;
501 ($changecb || sub {})->($lbl->get_active);
502 });
503
504 } elsif ($type eq 'sep') {
505 $table->attach_defaults (my $lbl1 = Gtk2::HSeparator->new, 0, 2, $idx, $idx + 1);
506 } else {
507 $edwid = Gtk2::Label->new ("FOO");
508 }
509 }
510
511 sub open_resize_map {
512 my ($self) = @_;
513
514 my $w = Gtk2::Window->new ('toplevel');
515 $w->set_default_size (250, 150);
516 $w->add (my $sw = Gtk2::ScrolledWindow->new);
517 $sw->add_with_viewport (my $v = Gtk2::VBox->new);
518 $sw->set_policy ('automatic', 'automatic');
519 $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
520
521 my $i = 0;
522 for (
523 [qw/width Width string/],
524 [qw/height Height string/],
525 [qw/save Save button/,
526 sub {
527 $self->{map}{map}->resize ($self->{map}{map}{info}{width}, $self->{map}{map}{info}{height});
528 $self->{map}->invalidate_all;
529 $w->destroy;
530 }
531 ],
532 )
533 {
534 $self->_add_prop_entry ($t, $i++, @$_);
535 }
536
537 $w->show_all;
538 }
539
540 sub follow {
541 my ($self, $dir) = @_;
542
543 my %dir_to_path = (
544 u => 'tile_path_1',
545 d => 'tile_path_3',
546 r => 'tile_path_2',
547 l => 'tile_path_4',
548 );
549
550 defined $dir_to_path{$dir}
551 or return;
552 my $map = $self->{map}{map}{info}{$dir_to_path{$dir}}
553 or return;
554
555 $map = map2abs ($map, $self);
556 $::MAINWIN->open_map_editor ($map);
557 }
558
559 sub open_map_prop {
560 my ($self) = @_;
561
562
563 my $w = Gtk2::Window->new ('toplevel');
564 $w->set_default_size (500, 500);
565 $w->add (my $sw = Gtk2::ScrolledWindow->new);
566 $sw->add_with_viewport (my $v = Gtk2::VBox->new);
567 $sw->set_policy ('automatic', 'automatic');
568 $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
569
570 my $i = 0;
571 for (
572 [qw/name Name string/],
573 [qw/region Region string/],
574 [qw/enter_x Enter-x string/],
575 [qw/enter_y Enter-y string/],
576 [qw/reset_timeout Reset-timeout string/],
577 [qw/swap_time Swap-timeout string/],
578 [qw/x x sep/],
579 [qw/difficulty Difficulty string/],
580 [qw/windspeed Windspeed string/],
581 [qw/pressure Pressure string/],
582 [qw/humid Humid string/],
583 [qw/temp Temp string/],
584 [qw/darkness Darkness string/],
585 [qw/sky Sky string/],
586 [qw/winddir Winddir string/],
587 [qw/x x sep/],
588 [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
589 [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
590 [qw/x x sep/],
591 [qw/msg Text text/],
592 [qw/maplore Maplore text/],
593 [qw/outdoor Outdoor check/],
594 [qw/unique Unique check/],
595 [qw/fixed_resettime Fixed-resettime check/],
596 [qw/x x sep/],
597 [qw/tile_path_1 Northpath string/],
598 [qw/tile_path_2 Eastpath string/],
599 [qw/tile_path_3 Southpath string/],
600 [qw/tile_path_4 Westpath string/],
601 [qw/tile_path_5 Toppath string/],
602 [qw/tile_path_6 Bottompath string/],
603 [qw/x x sep/],
604 ['x', 'For shop description look in the manual', 'button', sub { $::MAINWIN->show_help_window }],
605 [qw/shopmin Shopmin string/],
606 [qw/shopmax Shopmax string/],
607 [qw/shoprace Shoprace string/],
608 [qw/shopgreed Shopgreed string/],
609 [qw/shopitems Shopitems string/],
610 [qw/x x sep/],
611 ['safe_map', 'Safe map (CF+)', 'check'],
612 )
613 {
614 $self->_add_prop_entry ($t, $i++, @$_);
615 }
616
617 $w->show_all;
618 }
619
620 =head1 AUTHOR
621
622 Marc Lehmann <schmorp@schmorp.de>
623 http://home.schmorp.de/
624
625 Robin Redeker <elmex@ta-sa.org>
626 http://www.ta-sa.org/
627
628 =cut
629 1;
630