ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
(Generate patch)

Comparing deliantra/gde/GCE/MapEditor.pm (file contents):
Revision 1.37 by elmex, Thu Jun 1 14:33:14 2006 UTC vs.
Revision 1.66 by elmex, Fri Aug 31 09:08:36 2007 UTC

14use Crossfire::Map; 14use Crossfire::Map;
15use Crossfire::MapWidget; 15use Crossfire::MapWidget;
16 16
17use GCE::AttrEdit; 17use GCE::AttrEdit;
18use GCE::Util; 18use GCE::Util;
19use GCE::HashDialog;
20
21use POSIX qw/strftime/;
19 22
20use Glib::Object::Subclass 23use Glib::Object::Subclass
21 Gtk2::Window; 24 Gtk2::Window;
22 25
26use Storable qw/dclone/;
27
23use strict; 28use strict;
29
30#################################################################
31###### WINDOW MANAGEMENT ########################################
32#################################################################
33
34sub save_layout {
35 my ($self) = @_;
36
37 $self->{attach_editor}->save_layout if $self->{attach_editor};
38 $self->{map_properties}->save_layout if $self->{map_properties};
39 $self->{meta_info_win}->save_layout if $self->{meta_info_win};
40
41 $main::CFG->{map_info} = main::get_pos_and_size ($self->{map_info})
42 if $self->{map_info};
43}
44
45sub close_windows {
46 my ($self) = @_;
47
48 $self->{attach_editor}->destroy if $self->{attach_editor};
49 $self->{map_properties}->destroy if $self->{map_properties};
50 $self->{meta_info_win}->destroy if $self->{meta_info_win};
51}
52
53#################################################################
54###### MENU MANAGEMENT ##########################################
55#################################################################
24 56
25sub do_context_menu { 57sub do_context_menu {
26 my ($self, $map, $event) = @_; 58 my ($self, $map, $event) = @_;
27 59
28 my ($x, $y) = $map->coord ($event->x, $event->y); 60 my ($x, $y) = $map->coord ($event->x, $event->y);
30 my $menu = Gtk2::Menu->new; 62 my $menu = Gtk2::Menu->new;
31 foreach my $cm ( 63 foreach my $cm (
32 [ 64 [
33 Follow => sub { 65 Follow => sub {
34 $::MAINWIN->{edit_collection}{followexit}->edit ($map, $x, $y, $self) 66 $::MAINWIN->{edit_collection}{followexit}->edit ($map, $x, $y, $self)
35 } 67 },
36 ] 68 ]
37 ) { 69 ) {
38 my $item = Gtk2::MenuItem->new ($cm->[0]); 70 my $item = Gtk2::MenuItem->new ($cm->[0]);
39 $menu->append ($item); 71 $menu->append ($item);
40 $item->show; 72 $item->show;
41 $item->signal_connect (activate => $cm->[1]); 73 $item->signal_connect (activate => $cm->[1]);
42 } 74 }
75
76 $menu->append (my $sep = new Gtk2::SeparatorMenuItem);
77 $sep->show;
78
79 for my $sr (reverse $self->get_stack_refs ($map, $x, $y)) {
80 my $item = Gtk2::MenuItem->new ($sr->longname);
81 $menu->append ($item);
82 $item->set_submenu (my $smenu = new Gtk2::Menu);
83
84 for my $act (
85 [ 'Add inventory' => sub { $_[0]->add_inv ($::MAINWIN->get_pick) } ],
86 [ 'Find in picker' => sub { $::MAINWIN->open_pick_window ({ selection => $sr->picker_folder }) } ],
87 ) {
88 my $sitem = Gtk2::MenuItem->new ($act->[0]);
89 $smenu->append ($sitem);
90 $sitem->signal_connect (activate => sub { $act->[1]->($sr) });
91 $sitem->show;
92 }
93
94 $item->show;
95 }
96
43 $menu->popup (undef, undef, undef, undef, $event->button, $event->time); 97 $menu->popup (undef, undef, undef, undef, $event->button, $event->time);
44} 98}
45 99
46sub build_menu { 100sub build_menu {
47 my ($self) = @_; 101 my ($self) = @_;
55 accelerator => '<ctrl>S' 109 accelerator => '<ctrl>S'
56 }, 110 },
57 "Save As" => { 111 "Save As" => {
58 callback => sub { $self->save_map_as }, 112 callback => sub { $self->save_map_as },
59 }, 113 },
114 "Map _Info" => {
115 callback => sub { $self->open_map_info },
116 accelerator => "<ctrl>I",
117 },
60 "_Map Properties" => { 118 "Map _Properties" => {
61 callback => sub { $self->open_map_prop }, 119 callback => sub { $self->open_map_prop },
62 accelerator => "<ctrl>P" 120 accelerator => "<ctrl>P"
63 }, 121 },
122 "Map _Attachments" => {
123 callback => sub { $self->open_attach_edit },
124 accelerator => "<ctrl>A"
125 },
126 "Map Meta _Info" => {
127 callback => sub { $self->open_meta_info },
128 },
129 Upload => {
130 item_type => '<Branch>',
131 children => [
132 "Upload for testing" => {
133 callback => sub { $self->upload_map_test },
134 },
135 "Upload for inclusion" => {
136 callback => sub { $self->upload_map_incl },
137 },
138 ]
139 },
64 "_Map Resize" => { 140 "_Map Resize" => {
65 callback => sub { $self->open_resize_map }, 141 callback => sub { $self->open_resize_map },
66 accelerator => "<ctrl>R"
67 }, 142 },
68 "Close" => { 143 "Close" => {
69 callback => sub { $self->destroy }, 144 callback => sub { $self->destroy },
145 accelerator => "<ctrl>W",
70 }, 146 },
71 ] 147 ]
72 }, 148 },
73 _Edit => { 149 _Edit => {
74 item_type => '<Branch>', 150 item_type => '<Branch>',
134 my $tool = $_->[1]; 210 my $tool = $_->[1];
135 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible', 211 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
136 sub { $::MAINWIN->set_edit_tool ($tool) }); 212 sub { $::MAINWIN->set_edit_tool ($tool) });
137 } 213 }
138 214
215 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{'r'}, ['control-mask'], 'visible',
216 sub { $self->redo });
217
139 $self->add_accel_group ($men->{accel_group}); 218 $self->add_accel_group ($men->{accel_group});
140 219
141 return $men->{widget}; 220 return $men->{widget};
142} 221}
222
223#################################################################
224###### EDIT TOOL STUFF ##########################################
225#################################################################
143 226
144sub set_edit_tool { 227sub set_edit_tool {
145 my ($self, $tool) = @_; 228 my ($self, $tool) = @_;
146 229
147 $self->{etool} = $tool; 230 $self->{etool} = $tool;
157sub ea { 240sub ea {
158 my ($self) = @_; 241 my ($self) = @_;
159 $self->{ea_alt} || $self->{etool}; 242 $self->{ea_alt} || $self->{etool};
160} 243}
161 244
245sub start_drawmode {
246 my ($self, $map) = @_;
247
248 $self->{draw_mode} and return;
249
250 # XXX: is this okay? my ($x, $y) = $map->coord ($event->x, $event->y);
251 my ($x, $y) = $map->coord ($map->get_pointer);
252
253 my $ea = $self->ea;
254
255 $ea->begin ($map, $x, $y, $self);
256
257 $ea->edit ($map, $x, $y, $self)
258 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
259
260 $self->{draw_mode} = [$x, $y];
261}
262
263sub stop_drawmode {
264 my ($self, $map) = @_;
265
266 $self->{draw_mode} or return;
267
268 my ($x, $y) = $map->coord ($map->get_pointer);
269
270 my $ea = $self->ea;
271 $ea->end ($map, $x, $y, $self);
272
273 delete $self->{draw_mode};
274}
275
276#################################################################
277###### UTILITY FUNCTIONS ########################################
278#################################################################
279
280sub follow {
281 my ($self, $dir) = @_;
282
283 my %dir_to_path = (
284 u => 'tile_path_1',
285 d => 'tile_path_3',
286 r => 'tile_path_2',
287 l => 'tile_path_4',
288 );
289
290 defined $dir_to_path{$dir}
291 or return;
292 my $map = $self->{map}{map}{info}{$dir_to_path{$dir}}
293 or return;
294
295 $map = map2abs ($map, $self);
296 $::MAINWIN->open_map_editor ($map);
297}
298
299# FIXME: Fix the automatic update of the attribute editor! and also the stack view!
300sub undo {
301 my ($self) = @_;
302
303 my $map = $self->{map}; # the Crossfire::MapWidget
304
305 $map->{undo_stack_pos}
306 or return;
307
308 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
309}
310
311sub get_stack_refs {
312 my ($self, $map, $x, $y) = @_;
313
314 my $cstack = $map->get ($x, $y);
315
316 return [] unless @$cstack;
317
318 my @refs;
319
320 for my $arch (@$cstack) {
321 my ($ix, $iy, $iarch, $istack) = devirtualize ($map, $x, $y, $arch, $cstack);
322 push @refs,
323 GCE::ArchRef->new (
324 arch => $iarch,
325 source => 'map',
326 cb => sub {
327 $map->change_begin ('attredit');
328 $map->change_stack ($ix, $iy, $istack);
329
330 if (my $changeset = $map->change_end) {
331 splice @{ $map->{undo_stack} ||= [] },
332 $map->{undo_stack_pos}++, 1e6,
333 $changeset;
334 }
335 }
336 );
337 }
338
339 return @refs;
340}
341
342sub redo {
343 my ($self) = @_;
344
345 my $map = $self->{map}; # the Crossfire::MapWidget
346
347 $map->{undo_stack}
348 and $map->{undo_stack_pos} < @{$map->{undo_stack}}
349 or return;
350
351 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
352}
353
354sub load_meta_info {
355 my ($mapfile) = @_;
356 if (-e "$mapfile.meta") {
357 open my $metafh, "<", "$mapfile.meta"
358 or warn "Couldn't open meta file $mapfile.meta: $!";
359 my $metadata = do { local $/; <$metafh> };
360 return Crossfire::from_json ($metadata);
361 }
362}
363
364sub save_meta_info {
365 my ($mapfile, $metainfo) = @_;
366 open my $metafh, ">", "$mapfile.meta"
367 or warn "Couldn't write meta file $mapfile.meta: $!";
368 print $metafh Crossfire::to_json ($metainfo);
369}
370
371sub open_map {
372 my ($self, $path, $key) = @_;
373
374 $self->{mapkey} = $key;
375
376 if (ref $path) {
377 $self->{map}->set_map ($path);
378 delete $self->{meta_info};
379 $self->set_window_title;
380
381 } else {
382 my $ok = 0;
383 if (-e $path && -f $path) {
384 $ok = 1;
385 } else {
386 unless ($path =~ m/\.map$/) { # yuck
387 my $p = $path . '.map';
388 if ($ok = -e $p && -f $p) {
389 $path = $p;
390 }
391 }
392 }
393 unless ($ok) {
394 die "Couldn't open '$path' or find '$path.map': No such file or it is not a file.\n";
395 }
396 $self->{path} = $path;
397 $self->{map}->set_map (my $m = new_from_file Crossfire::Map $path);
398 $self->{meta_info} = load_meta_info ($path);
399 $self->set_window_title ($self->{path});
400 $::MAINWIN->add_recent($path);
401 }
402 $self->update_overlays;
403 $self->close_windows;
404}
405
406sub save_map {
407 my ($self) = @_;
408
409 if ($self->{path}) {
410 $self->{map}{map}->write_file ($self->{path});
411 if ($self->{meta_info}) {
412 save_meta_info ($self->{path}, $self->{meta_info});
413 }
414 quick_msg ($self, "saved to $self->{path}");
415 $self->set_window_title ($self->{path});
416 $::MAINWIN->add_recent($self->{path});
417 } else {
418 $self->save_map_as;
419 }
420}
421
422sub set_window_title {
423 my ($self, $title) = @_;
424
425 $title = 'Unsaved'
426 unless $title;
427
428 $self->set_title(File::Basename::basename($title).' - gcrossedit');
429}
430
431sub save_map_as {
432 my ($self) = @_;
433
434 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
435
436 if ('ok' eq $fc->run) {
437
438 $::MAINWIN->{fc_last_folder} = $fc->get_current_folder;
439 $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++;
440
441 if($fc->get_filename) {
442 $self->{path} = $fc->get_filename;
443 $self->save_map;
444 }
445 }
446
447 $fc->destroy;
448}
449
450sub _get_conns_for_obj {
451 my ($obj, $x, $y, $rconns) = @_;
452
453 if (defined $obj->{connected}) {
454 $rconns->{$x}->{$y}->{$obj->{connected}} = 1;
455
456 } elsif (defined $obj->{msg}) {
457 my $msg = $obj->{msg};
458
459 while ($msg =~ s/\@trigger\s+(\d+)//) {
460 $rconns->{$x}->{$y}->{$1} = 1;
461 }
462 }
463}
464
465sub update_overlays {
466 my ($self, $sx, $sy, $stack) = @_;
467 my $conns = {};
468
469 if (not $stack) {
470 my ($w, $h) = ($self->{map}{map}{width}, $self->{map}{map}{height});
471
472 for (my $x = 0; $x < $w; $x++) {
473 for (my $y = 0; $y < $h; $y++) {
474 _get_conns_for_obj ($_, $x, $y, $conns)
475 for @{$self->{map}->get ($x, $y)};
476 }
477 }
478
479 # delete prev. overlays
480 for (keys %{$self->{_conn_overlays}}) {
481 $self->{map}->overlay ($_);
482 }
483 } else {
484 # del old overlay for this place
485 my $ovl = "connection_$sx\_$sy";
486 $self->{map}->overlay ($ovl) if delete $self->{_conn_overlays}->{$ovl};
487 _get_conns_for_obj ($_, $sx, $sy, $conns)
488 for @$stack;
489 }
490
491 # put new overlays there
492 for my $x (keys %$conns) {
493 for my $y (keys %{$conns->{$x}}) {
494 my $ovlname = "connection_$x\_$y";
495 my $conns_ovl = join (', ', keys %{$conns->{$x}->{$y}});
496 $self->{_conn_overlays}->{$ovlname} = 1;
497 my ($a, $t, $ac)
498 = Gtk2::Pango->parse_markup (
499 "<span size=\"xx-small\">$conns_ovl</span>", ''
500 );
501 my $pl = $self->{map}->create_pango_layout ('');
502 $pl->set_attributes ($a);
503 $pl->set_text ($t);
504 my ($ink_rect, $logical_rect) = $pl->get_pixel_extents;
505
506 $self->{map}->overlay (
507 $ovlname, $x * TILESIZE, $y * TILESIZE, TILESIZE, TILESIZE,
508 sub {
509 my ($mapwin, $x, $y) = @_;
510 if (!$self->{_conn_upd_ovl_gc_fg}) {
511 my $gc
512 = $self->{_conn_upd_ovl_gc_fg}
513 = Gtk2::Gdk::GC->new ($mapwin->{window});
514 my $cm = $mapwin->{window}->get_colormap;
515 $gc->set_foreground (gtk2_get_color ($mapwin, "yellow"));
516 $gc->set_background (gtk2_get_color ($mapwin, "black"));
517 }
518 $mapwin->{window}->draw_rectangle (
519 $mapwin->style->black_gc,
520 1,
521 $x, $y, $logical_rect->{width} + 2, $logical_rect->{height} + 2,
522 );
523 $mapwin->{window}->draw_rectangle (
524 $self->{_conn_upd_ovl_gc_fg},
525 0,
526 $x, $y, $logical_rect->{width} + 2, $logical_rect->{height} + 2,
527 );
528 $mapwin->{window}->draw_layout_with_colors (
529 $mapwin->style->black_gc, $x + 1, $y + 1, $pl,
530 $self->{connection_overlay_foreground},
531 $self->{connection_overlay_background},
532 )
533 }
534 );
535 }
536 }
537}
538
539#################################################################
540###### DIALOGOUES ###############################################
541#################################################################
542
543sub open_resize_map {
544 my ($self) = @_;
545
546 return if $self->{meta_info_win};
547
548 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
549
550 $w->init (
551 dialog_default_size => [500, 200, 220, 20],
552 layout_name => 'resize_win',
553 title => 'resize map',
554 ref_hash => $self->{map}{map}{info},
555 dialog => [
556 [width => 'Width' => 'string'],
557 [height => 'Height' => 'string'],
558 ],
559 close_on_save => 1,
560 save_button_label => 'resize',
561 save_cb => sub {
562 my ($info) = @_;
563 $self->{map}{map}->resize ($info->{width}, $info->{height});
564 $self->{map}->set_map ($self->{map}{map});
565 $self->update_overlays;
566 }
567 );
568
569 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
570
571 $w->show_all;
572}
573
574sub open_attach_edit {
575 my ($self) = @_;
576
577 my $w = GCE::AttachEditor->new;
578 $w->set_attachment (
579 $self->{map}{map}{info}{attach},
580 sub {
581 if (@{$_[0]}) {
582 $self->{map}{map}{info}{attach} = $_[0]
583 } else {
584 delete $self->{map}{map}{info}{attach};
585 }
586 }
587 );
588 $self->{attach_editor} = $w;
589 $w->signal_connect (destroy => sub { delete $self->{attach_editor} });
590 $w->show_all;
591}
592
593sub upload_map_incl {
594 my ($self) = @_;
595
596 my $meta = dclone $self->{meta_info};
597
598 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
599
600 $w->init (
601 dialog_default_size => [500, 300, 220, 20],
602 layout_name => 'map_upload_incl',
603 title => 'gce - map inclusion upload',
604 ref_hash => $meta,
605 text_entry => { key => 'changes', label => 'Changes (required for inclusion):' },
606 dialog => [
607 [gameserver => 'Game server' => 'label'],
608 [testserver => 'Test server' => 'label'],
609 [undef => x => 'sep' ],
610 [cf_login => 'Server login name' => 'string'],
611 [cf_password=> 'Password' => 'password'],
612 [path => 'Map path' => 'string'],
613 ],
614 close_on_save => 1,
615 save_cb => sub {
616 my ($meta) = @_;
617 warn "UPLOAD[".Crossfire::to_json ($meta)."]\n";
618 }
619 );
620
621 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
622
623 $w->show_all;
624}
625
626sub upload_map_test {
627 my ($self) = @_;
628
629 my $meta = dclone $self->{meta_info};
630
631 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
632
633 $w->init (
634 dialog_default_size => [500, 300, 220, 20],
635 layout_name => 'map_upload_test',
636 title => 'gce - map test upload',
637 ref_hash => $meta,
638 dialog => [
639 [gameserver => 'Game server' => 'string'],
640 [testserver => 'Test server' => 'string'],
641 [undef => x => 'sep' ],
642 [cf_login => 'Server login name' => 'string'],
643 [cf_password=> 'Password' => 'password'],
644 [path => 'Map path' => 'string'],
645 ],
646 save_cb => sub {
647 my ($meta) = @_;
648 warn "UPLOAD[".Crossfire::to_json ($meta)."]\n";
649 }
650 );
651
652 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
653
654 $w->show_all;
655
656
657}
658
659sub open_meta_info {
660 my ($self) = @_;
661
662 return if $self->{meta_info_win};
663
664 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
665
666 $w->init (
667 dialog_default_size => [500, 300, 220, 20],
668 layout_name => 'meta_info_win',
669 title => 'meta info',
670 ref_hash => $self->{meta_info},
671 dialog => [
672 [path => 'Map path' => 'string'],
673 [cf_login => 'Login name' => 'string'],
674 [revision => 'CVS Revision' => 'label'],
675 [cvs_root => 'CVS Root' => 'label'],
676 [lib_root => 'LIB Root' => 'label'],
677 [testserver => 'Test server' => 'label'],
678 [gameserver => 'Game server' => 'label'],
679 ],
680 );
681
682 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
683
684 $w->show_all;
685}
686
687sub open_map_info {
688 my ($self) = @_;
689 return if $self->{map_info};
690
691 my $w = $self->{map_info} = Gtk2::Window->new ('toplevel');
692 $w->set_title ("gcrossedit - map info");
693
694 $w->add (my $vb = Gtk2::VBox->new);
695 $vb->add (my $sw = Gtk2::ScrolledWindow->new);
696 $sw->set_policy ('automatic', 'automatic');
697 $sw->add (my $txt = Gtk2::TextView->new);
698 $vb->pack_start (my $hb = Gtk2::HBox->new (1, 1), 0, 1, 0);
699 $hb->pack_start (my $svbtn = Gtk2::Button->new ("save"), 1, 1, 0);
700 $hb->pack_start (my $logbtn = Gtk2::Button->new ("add log"), 1, 1, 0);
701 $hb->pack_start (my $closebtn = Gtk2::Button->new ("close"), 1, 1, 0);
702
703 my $buf = $txt->get_buffer ();
704 $buf->set_text ($self->{map}{map}{info}{msg});
705
706 $svbtn->signal_connect (clicked => sub {
707 my $buf = $txt->get_buffer ();
708 my $txt = $buf->get_text ($buf->get_start_iter, $buf->get_end_iter, 0);
709 $self->{map}{map}{info}{msg} = $txt;
710 });
711
712 $logbtn->signal_connect (clicked => sub {
713 my $buf = $txt->get_buffer ();
714 $buf->insert ($buf->get_start_iter, "- " . strftime ("%F %T %Z", localtime (time)) . " by " . ($main::CFG->{username} || $ENV{USER}) . ":\n");
715 $txt->set_buffer ($buf);
716 });
717
718 $closebtn->signal_connect (clicked => sub {
719 $w->destroy;
720 });
721
722 ::set_pos_and_size ($w, $main::CFG->{map_info}, 400, 400, 220, 20);
723 $w->signal_connect (destroy => sub {
724 delete $self->{map_info};
725 });
726 $w->show_all;
727}
728
729sub open_map_prop {
730 my ($self) = @_;
731
732 return if $self->{map_properties};
733
734 my $w = $self->{map_properties} = GCE::HashDialogue->new ();
735
736 $w->init (
737 dialog_default_size => [500, 500, 220, 20],
738 layout_name => 'map_prop_win',
739 title => 'map properties',
740 ref_hash => $self->{map}{map}{info},
741 close_on_save => 1,
742 dialog => [
743 [qw/name Name string/],
744 [qw/region Region string/],
745 [qw/enter_x Enter-x string/],
746 [qw/enter_y Enter-y string/],
747 [qw/reset_timeout Reset-timeout string/],
748 [qw/swap_time Swap-timeout string/],
749 [undef, qw/x sep/],
750 [qw/difficulty Difficulty string/],
751 [qw/windspeed Windspeed string/],
752 [qw/pressure Pressure string/],
753 [qw/humid Humid string/],
754 [qw/temp Temp string/],
755 [qw/darkness Darkness string/],
756 [qw/sky Sky string/],
757 [qw/winddir Winddir string/],
758 [undef, qw/x sep/],
759 [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
760 [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
761 [undef, qw/x sep/],
762 # [qw/msg Text text/],
763# [qw/maplore Maplore text/],
764 [qw/outdoor Outdoor check/],
765 [qw/unique Unique check/],
766 [qw/fixed_resettime Fixed-resettime check/],
767 [per_player => 'Per player' => 'check'],
768 [per_party => 'Per party' => 'check'],
769 [no_reset => 'No reset' => 'check'],
770 [music => 'Map Music' => 'string'],
771 [undef, qw/x sep/],
772 [qw/tile_path_1 Northpath string/],
773 [qw/tile_path_2 Eastpath string/],
774 [qw/tile_path_3 Southpath string/],
775 [qw/tile_path_4 Westpath string/],
776 [qw/tile_path_5 Toppath string/],
777 [qw/tile_path_6 Bottompath string/],
778 [undef, qw/x sep/],
779 [undef, 'For shop description look in the manual',
780 'button', sub { $::MAINWIN->show_help_window }],
781 [qw/shopmin Shopmin string/],
782 [qw/shopmax Shopmax string/],
783 [qw/shoprace Shoprace string/],
784 [qw/shopgreed Shopgreed string/],
785 [qw/shopitems Shopitems string/],
786 ]
787 );
788
789 $w->signal_connect (destroy => sub { delete $self->{map_properties} });
790 $w->show_all;
791}
792
793#################################################################
794###### MAP EDITOR INIT ##########################################
795#################################################################
796
162sub INIT_INSTANCE { 797sub INIT_INSTANCE {
163 my ($self) = @_; 798 my ($self) = @_;
164 799
165 $self->set_title ('gce - map editor'); 800 $self->set_window_title;
166 $self->add (my $vb = Gtk2::VBox->new); 801 $self->add (my $vb = Gtk2::VBox->new);
167 802
168 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0); 803 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
169 804
170 $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0); 805 $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0);
806
807 $map->signal_connect_after (stack_change => sub {
808 my ($map, $x, $y, $stack) = @_;
809 $self->update_overlays ($x, $y, $stack);
810 });
811 $map->signal_connect_after (swap_stack_change => sub {
812 my ($map, $x, $y, $stack) = @_;
813 $self->update_overlays ($x, $y, $stack);
814 });
815
816 $self->{connection_overlay_foreground}
817 = Gtk2::Gdk::Color->new (257 * 255, 257 * 255, 0);
818 $self->{connection_overlay_background}
819 = Gtk2::Gdk::Color->new (0, 0, 0);
820 # my $ygc
821 # = $self->{connection_overlay_yellow_gc}
822 # = Gtk2::Gdk::GC->new ($self->{map}{window});
823 # $ygc->set_foreground (Gtk2::Gdk::Color->new (257 * 255, 257 * 255, 0));
171 824
172 $map->signal_connect_after (key_press_event => sub { 825 $map->signal_connect_after (key_press_event => sub {
173 my ($map, $event) = @_; 826 my ($map, $event) = @_;
174 827
175 my $kv = $event->keyval; 828 my $kv = $event->keyval;
275 $X++ if $X < $x; 928 $X++ if $X < $x;
276 $X-- if $X > $x; 929 $X-- if $X > $x;
277 $Y++ if $Y < $y; 930 $Y++ if $Y < $y;
278 $Y-- if $Y > $y; 931 $Y-- if $Y > $y;
279 932
933 unless ($ea->only_on_click) {
280 $ea->edit ($map, $X, $Y, $self) 934 $ea->edit ($map, $X, $Y, $self)
281 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height}; 935 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
936 }
282 } 937 }
283 938
284 @{$self->{draw_mode}}[0,1] = ($X, $Y); 939 @{$self->{draw_mode}}[0,1] = ($X, $Y);
285 940
286 1 941 1
300 return 1; 955 return 1;
301 } 956 }
302 957
303 0 958 0
304 }); 959 });
305}
306 960
307sub start_drawmode { 961 ::set_pos_and_size ($self, $main::CFG->{map_window}, 500, 500, 200, 0);
308 my ($self, $map) = @_;
309
310 $self->{draw_mode} and return;
311
312 # XXX: is this okay? my ($x, $y) = $map->coord ($event->x, $event->y);
313 my ($x, $y) = $map->coord ($map->get_pointer);
314
315 my $ea = $self->ea;
316
317 $ea->begin ($map, $x, $y, $self);
318
319 $ea->edit ($map, $x, $y, $self)
320 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
321
322 $self->{draw_mode} = [$x, $y];
323}
324
325sub stop_drawmode {
326 my ($self, $map) = @_;
327
328 $self->{draw_mode} or return;
329
330 my ($x, $y) = $map->coord ($map->get_pointer);
331
332 my $ea = $self->ea;
333 $ea->end ($map, $x, $y, $self);
334
335 delete $self->{draw_mode};
336}
337
338# FIXME: Fix the automatic update of the attribute editor! and also the stack view!
339sub undo {
340 my ($self) = @_;
341
342 my $map = $self->{map}; # the Crossfire::MapWidget
343
344 $map->{undo_stack_pos}
345 or return;
346
347 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
348}
349
350sub redo {
351 my ($self) = @_;
352
353 my $map = $self->{map}; # the Crossfire::MapWidget
354
355 $map->{undo_stack}
356 and $map->{undo_stack_pos} < @{$map->{undo_stack}}
357 or return;
358
359 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
360}
361
362sub open_map {
363 my ($self, $path, $key) = @_;
364
365 $self->{mapkey} = $key;
366
367 if (ref $path) {
368 $self->{map}->set_map ($path);
369
370 } else {
371 $self->{path} = $path;
372# print "OPENMAP $path\n";
373 $self->{map}->set_map (my $m = new_from_file Crossfire::Map $path);
374 require Data::Dumper;
375# print "FOO:" .Data::Dumper::Dumper ($m) . "\n";
376 }
377}
378
379sub save_map {
380 my ($self) = @_;
381
382 if ($self->{path}) {
383 $self->{map}{map}->write_file ($self->{path});
384 quick_msg ($self, "saved to $self->{path}");
385 } else {
386 $self->save_map_as;
387 }
388}
389
390sub save_map_as {
391 my ($self) = @_;
392
393 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
394
395 if ('ok' eq $fc->run) {
396
397 $::MAINWIN->{fc_last_folder} = $fc->get_current_folder;
398 $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++;
399
400 $self->{map}{map}->write_file ($self->{path} = $fc->get_filename);
401 quick_msg ($self, "saved to $self->{path}");
402 }
403
404 $fc->destroy;
405}
406
407sub _add_prop_entry {
408 my ($self, $table, $idx, $key, $desc, $type, $changecb) = @_;
409
410 my $edwid;
411
412 if ($type eq 'string') {
413 $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
414 $edwid = Gtk2::Entry->new;
415 $edwid->set_text ($self->{map}{map}{info}{$key});
416 $edwid->signal_connect (changed => sub {
417 $self->{map}{map}{info}{$key} = $_[0]->get_text;
418 if ($changecb) {
419 $changecb->($_[0]->get_text);
420 }
421 });
422 $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
423
424 } elsif ($type eq 'button') {
425 $table->attach_defaults (my $b = Gtk2::Button->new_with_label ($desc), 0, 2, $idx, $idx + 1);
426 $b->signal_connect (clicked => ($changecb || sub {}));
427
428 } elsif ($type eq 'label') {
429 $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
430 $edwid = Gtk2::Label->new ($self->{map}{map}{info}{$key});
431 $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
432
433 } elsif ($type eq 'check') {
434 $table->attach_defaults (my $lbl1 = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
435 $table->attach_defaults (my $lbl = Gtk2::CheckButton->new, 1, 2, $idx, $idx + 1);
436 $lbl->set_active ($self->{map}{map}{info}{$key});
437 $lbl->signal_connect (toggled => sub {
438 my ($lbl) = @_;
439 $self->{map}{map}{info}{$key} = $lbl->get_active * 1;
440 ($changecb || sub {})->($lbl->get_active);
441 });
442
443 } elsif ($type eq 'sep') {
444 $table->attach_defaults (my $lbl1 = Gtk2::HSeparator->new, 0, 2, $idx, $idx + 1);
445 } else {
446 $edwid = Gtk2::Label->new ("FOO");
447 }
448}
449
450sub open_resize_map {
451 my ($self) = @_;
452
453 my $w = Gtk2::Window->new ('toplevel');
454 $w->set_default_size (250, 150);
455 $w->add (my $sw = Gtk2::ScrolledWindow->new);
456 $sw->add_with_viewport (my $v = Gtk2::VBox->new);
457 $sw->set_policy ('automatic', 'automatic');
458 $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
459
460 my $i = 0;
461 for (
462 [qw/width Width string/],
463 [qw/height Height string/],
464 [qw/save Save button/,
465 sub {
466 $self->{map}{map}->resize ($self->{map}{map}{info}{width}, $self->{map}{map}{info}{height});
467 $self->{map}->invalidate_all;
468 $w->destroy;
469 }
470 ],
471 )
472 {
473 $self->_add_prop_entry ($t, $i++, @$_);
474 }
475
476 $w->show_all;
477}
478
479sub follow {
480 my ($self, $dir) = @_;
481
482 my %dir_to_path = (
483 u => 'tile_path_1',
484 d => 'tile_path_3',
485 r => 'tile_path_2',
486 l => 'tile_path_4',
487 );
488
489 defined $dir_to_path{$dir}
490 or return;
491 my $map = $self->{map}{map}{info}{$dir_to_path{$dir}}
492 or return;
493
494 $map = map2abs ($map, $self);
495 $::MAINWIN->open_map_editor ($map);
496}
497
498sub open_map_prop {
499 my ($self) = @_;
500
501
502 my $w = Gtk2::Window->new ('toplevel');
503 $w->set_default_size (500, 500);
504 $w->add (my $sw = Gtk2::ScrolledWindow->new);
505 $sw->add_with_viewport (my $v = Gtk2::VBox->new);
506 $sw->set_policy ('automatic', 'automatic');
507 $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
508
509 my $i = 0;
510 for (
511 [qw/name Name string/],
512 [qw/region Region string/],
513 [qw/enter_x Enter-x string/],
514 [qw/enter_y Enter-y string/],
515 [qw/reset_timeout Reset-timeout string/],
516 [qw/swap_time Swap-timeout string/],
517 [qw/x x sep/],
518 [qw/difficulty Difficulty string/],
519 [qw/windspeed Windspeed string/],
520 [qw/pressure Pressure string/],
521 [qw/humid Humid string/],
522 [qw/temp Temp string/],
523 [qw/darkness Darkness string/],
524 [qw/sky Sky string/],
525 [qw/winddir Winddir string/],
526 [qw/x x sep/],
527 [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
528 [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
529 [qw/x x sep/],
530 [qw/msg Text text/],
531 [qw/maplore Maplore text/],
532 [qw/outdoor Outdoor check/],
533 [qw/unique Unique check/],
534 [qw/fixed_resettime Fixed-resettime check/],
535 [qw/x x sep/],
536 [qw/tile_path_1 Northpath string/],
537 [qw/tile_path_2 Eastpath string/],
538 [qw/tile_path_3 Southpath string/],
539 [qw/tile_path_4 Westpath string/],
540 [qw/tile_path_5 Toppath string/],
541 [qw/tile_path_6 Bottompath string/],
542 )
543 {
544 $self->_add_prop_entry ($t, $i++, @$_);
545 }
546
547 $w->show_all;
548} 962}
549 963
550=head1 AUTHOR 964=head1 AUTHOR
551 965
552 Marc Lehmann <schmorp@schmorp.de> 966 Marc Lehmann <schmorp@schmorp.de>
554 968
555 Robin Redeker <elmex@ta-sa.org> 969 Robin Redeker <elmex@ta-sa.org>
556 http://www.ta-sa.org/ 970 http://www.ta-sa.org/
557 971
558=cut 972=cut
5591;
560 973
9741
975

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines