ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.43
Committed: Sat Oct 14 15:18:46 2006 UTC (17 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.42: +30 -3 lines
Log Message:
added meta data loading, adjusted environment variable handling

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