ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.65
Committed: Fri Aug 31 08:28:09 2007 UTC (16 years, 9 months ago) by elmex
Branch: MAIN
Changes since 1.64: +2 -2 lines
Log Message:
fixed a major slowdown

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 use GCE::HashDialog;
20
21 use POSIX qw/strftime/;
22
23 use Glib::Object::Subclass
24 Gtk2::Window;
25
26 use Storable qw/dclone/;
27
28 use strict;
29
30 #################################################################
31 ###### WINDOW MANAGEMENT ########################################
32 #################################################################
33
34 sub 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
45 sub 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 #################################################################
56
57 sub do_context_menu {
58 my ($self, $map, $event) = @_;
59
60 my ($x, $y) = $map->coord ($event->x, $event->y);
61
62 my $menu = Gtk2::Menu->new;
63 foreach my $cm (
64 [
65 Follow => sub {
66 $::MAINWIN->{edit_collection}{followexit}->edit ($map, $x, $y, $self)
67 },
68 ]
69 ) {
70 my $item = Gtk2::MenuItem->new ($cm->[0]);
71 $menu->append ($item);
72 $item->show;
73 $item->signal_connect (activate => $cm->[1]);
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
97 $menu->popup (undef, undef, undef, undef, $event->button, $event->time);
98 }
99
100 sub build_menu {
101 my ($self) = @_;
102
103 my $menu_tree = [
104 _File => {
105 item_type => '<Branch>',
106 children => [
107 "_Save" => {
108 callback => sub { $self->save_map },
109 accelerator => '<ctrl>S'
110 },
111 "Save As" => {
112 callback => sub { $self->save_map_as },
113 },
114 "Map _Info" => {
115 callback => sub { $self->open_map_info },
116 accelerator => "<ctrl>I",
117 },
118 "Map _Properties" => {
119 callback => sub { $self->open_map_prop },
120 accelerator => "<ctrl>P"
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 },
140 "_Map Resize" => {
141 callback => sub { $self->open_resize_map },
142 },
143 "Close" => {
144 callback => sub { $self->destroy },
145 accelerator => "<ctrl>W",
146 },
147 ]
148 },
149 _Edit => {
150 item_type => '<Branch>',
151 children => [
152 "_Undo" => {
153 callback => sub { $self->undo },
154 accelerator => "<ctrl>Z"
155 },
156 "_Redo" => {
157 callback => sub { $self->redo },
158 accelerator => "<ctrl>Y"
159 },
160 ]
161 },
162 _Go => {
163 item_type => '<Branch>',
164 children => [
165 "_Up" => {
166 callback => sub { $self->follow ('u') },
167 accelerator => "<ctrl>Up"
168 },
169 "_Down" => {
170 callback => sub { $self->follow ('d') },
171 accelerator => "<ctrl>Down"
172 },
173 "_Right" => {
174 callback => sub { $self->follow ('r') },
175 accelerator => "<ctrl>Right"
176 },
177 "_Left" => {
178 callback => sub { $self->follow ('l') },
179 accelerator => "<ctrl>Left"
180 },
181 ]
182 },
183 _Help => {
184 item_type => '<Branch>',
185 children => [
186 _Manual => {
187 callback => sub { $::MAINWIN->show_help_window },
188 accelerator => "<ctrl>H"
189 },
190 ]
191 },
192 ];
193
194 my $men =
195 Gtk2::SimpleMenu->new (
196 menu_tree => $menu_tree,
197 default_callback => \&default_cb,
198 );
199
200 for (
201 [i => 'pick'],
202 [p => 'place'],
203 [e => 'erase'],
204 [s => 'select'],
205 [l => 'eval'],
206 [t => 'connect'],
207 [f => 'followexit']
208 )
209 {
210 my $tool = $_->[1];
211 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
212 sub { $::MAINWIN->set_edit_tool ($tool) });
213 }
214
215 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{'r'}, ['control-mask'], 'visible',
216 sub { $self->redo });
217
218 $self->add_accel_group ($men->{accel_group});
219
220 return $men->{widget};
221 }
222
223 #################################################################
224 ###### EDIT TOOL STUFF ##########################################
225 #################################################################
226
227 sub set_edit_tool {
228 my ($self, $tool) = @_;
229
230 $self->{etool} = $tool;
231
232 if ($self->ea->special_arrow) {
233 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
234 } else {
235 # FIXME: Get the original cursor and insert it here
236 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
237 }
238 }
239
240 sub ea {
241 my ($self) = @_;
242 $self->{ea_alt} || $self->{etool};
243 }
244
245 sub 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
263 sub 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
280 sub 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!
300 sub 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
311 sub 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
342 sub 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
354 sub 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
364 sub 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
371 sub 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
406 sub 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
422 sub 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
431 sub 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
450 sub update_overlays {
451 my ($self, $sx, $sy, $stack) = @_;
452 my $conns = {};
453
454 if (not $stack) {
455 my ($w, $h) = ($self->{map}{map}{width}, $self->{map}{map}{height});
456
457 for (my $x = 0; $x <= $w; $x++) {
458 for (my $y = 0; $y <= $h; $y++) {
459 my $stk = $self->{map}->get ($x, $y);
460 for (@$stk) {
461 if (defined $_->{connected}) {
462 $conns->{$x}->{$y}->{$_->{connected}} = 1;
463 }
464 }
465 }
466 }
467
468 # delete prev. overlays
469 for (keys %{$self->{_conn_overlays}}) {
470 $self->{map}->overlay ($_);
471 }
472 } else {
473 # del old overlay for this place
474 my $ovl = "connection_$sx\_$sy";
475 $self->{map}->overlay ($ovl) if delete $self->{_conn_overlays}->{$ovl};
476 for (@$stack) {
477 if (defined $_->{connected}) {
478 $conns->{$sx}->{$sy}->{$_->{connected}} = 1;
479 }
480 }
481 }
482
483 # put new overlays there
484 for my $x (keys %$conns) {
485 for my $y (keys %{$conns->{$x}}) {
486 my $ovlname = "connection_$x\_$y";
487 my $conns_ovl = join (', ', keys %{$conns->{$x}->{$y}});
488 $self->{_conn_overlays}->{$ovlname} = 1;
489 my ($a, $t, $ac)
490 = Gtk2::Pango->parse_markup (
491 "<span size=\"xx-small\">$conns_ovl</span>", ''
492 );
493 my $pl = $self->{map}->create_pango_layout ('');
494 $pl->set_attributes ($a);
495 $pl->set_text ($t);
496 my ($ink_rect, $logical_rect) = $pl->get_pixel_extents;
497
498 $self->{map}->overlay (
499 $ovlname, $x * TILESIZE, $y * TILESIZE, TILESIZE, TILESIZE,
500 sub {
501 my ($mapwin, $x, $y) = @_;
502 if (!$self->{_conn_upd_ovl_gc_fg}) {
503 my $gc
504 = $self->{_conn_upd_ovl_gc_fg}
505 = Gtk2::Gdk::GC->new ($mapwin->{window});
506 my $cm = $mapwin->{window}->get_colormap;
507 $gc->set_foreground (gtk2_get_color ($mapwin, "yellow"));
508 $gc->set_background (gtk2_get_color ($mapwin, "black"));
509 }
510 $mapwin->{window}->draw_rectangle (
511 $mapwin->style->black_gc,
512 1,
513 $x, $y, $logical_rect->{width} + 2, $logical_rect->{height} + 2,
514 );
515 $mapwin->{window}->draw_rectangle (
516 $self->{_conn_upd_ovl_gc_fg},
517 0,
518 $x, $y, $logical_rect->{width} + 2, $logical_rect->{height} + 2,
519 );
520 $mapwin->{window}->draw_layout_with_colors (
521 $mapwin->style->black_gc, $x + 1, $y + 1, $pl,
522 $self->{connection_overlay_foreground},
523 $self->{connection_overlay_background},
524 )
525 }
526 );
527 }
528 }
529 }
530
531 #################################################################
532 ###### DIALOGOUES ###############################################
533 #################################################################
534
535 sub open_resize_map {
536 my ($self) = @_;
537
538 return if $self->{meta_info_win};
539
540 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
541
542 $w->init (
543 dialog_default_size => [500, 200, 220, 20],
544 layout_name => 'resize_win',
545 title => 'resize map',
546 ref_hash => $self->{map}{map}{info},
547 dialog => [
548 [width => 'Width' => 'string'],
549 [height => 'Height' => 'string'],
550 ],
551 close_on_save => 1,
552 save_button_label => 'resize',
553 save_cb => sub {
554 my ($info) = @_;
555 $self->{map}{map}->resize ($info->{width}, $info->{height});
556 $self->{map}->set_map ($self->{map}{map});
557 $self->update_overlays;
558 }
559 );
560
561 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
562
563 $w->show_all;
564 }
565
566 sub open_attach_edit {
567 my ($self) = @_;
568
569 my $w = GCE::AttachEditor->new;
570 $w->set_attachment (
571 $self->{map}{map}{info}{attach},
572 sub {
573 if (@{$_[0]}) {
574 $self->{map}{map}{info}{attach} = $_[0]
575 } else {
576 delete $self->{map}{map}{info}{attach};
577 }
578 }
579 );
580 $self->{attach_editor} = $w;
581 $w->signal_connect (destroy => sub { delete $self->{attach_editor} });
582 $w->show_all;
583 }
584
585 sub upload_map_incl {
586 my ($self) = @_;
587
588 my $meta = dclone $self->{meta_info};
589
590 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
591
592 $w->init (
593 dialog_default_size => [500, 300, 220, 20],
594 layout_name => 'map_upload_incl',
595 title => 'gce - map inclusion upload',
596 ref_hash => $meta,
597 text_entry => { key => 'changes', label => 'Changes (required for inclusion):' },
598 dialog => [
599 [gameserver => 'Game server' => 'label'],
600 [testserver => 'Test server' => 'label'],
601 [undef => x => 'sep' ],
602 [cf_login => 'Server login name' => 'string'],
603 [cf_password=> 'Password' => 'password'],
604 [path => 'Map path' => 'string'],
605 ],
606 close_on_save => 1,
607 save_cb => sub {
608 my ($meta) = @_;
609 warn "UPLOAD[".Crossfire::to_json ($meta)."]\n";
610 }
611 );
612
613 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
614
615 $w->show_all;
616 }
617
618 sub upload_map_test {
619 my ($self) = @_;
620
621 my $meta = dclone $self->{meta_info};
622
623 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
624
625 $w->init (
626 dialog_default_size => [500, 300, 220, 20],
627 layout_name => 'map_upload_test',
628 title => 'gce - map test upload',
629 ref_hash => $meta,
630 dialog => [
631 [gameserver => 'Game server' => 'string'],
632 [testserver => 'Test server' => 'string'],
633 [undef => x => 'sep' ],
634 [cf_login => 'Server login name' => 'string'],
635 [cf_password=> 'Password' => 'password'],
636 [path => 'Map path' => 'string'],
637 ],
638 save_cb => sub {
639 my ($meta) = @_;
640 warn "UPLOAD[".Crossfire::to_json ($meta)."]\n";
641 }
642 );
643
644 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
645
646 $w->show_all;
647
648
649 }
650
651 sub open_meta_info {
652 my ($self) = @_;
653
654 return if $self->{meta_info_win};
655
656 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
657
658 $w->init (
659 dialog_default_size => [500, 300, 220, 20],
660 layout_name => 'meta_info_win',
661 title => 'meta info',
662 ref_hash => $self->{meta_info},
663 dialog => [
664 [path => 'Map path' => 'string'],
665 [cf_login => 'Login name' => 'string'],
666 [revision => 'CVS Revision' => 'label'],
667 [cvs_root => 'CVS Root' => 'label'],
668 [lib_root => 'LIB Root' => 'label'],
669 [testserver => 'Test server' => 'label'],
670 [gameserver => 'Game server' => 'label'],
671 ],
672 );
673
674 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
675
676 $w->show_all;
677 }
678
679 sub open_map_info {
680 my ($self) = @_;
681 return if $self->{map_info};
682
683 my $w = $self->{map_info} = Gtk2::Window->new ('toplevel');
684 $w->set_title ("gcrossedit - map info");
685
686 $w->add (my $vb = Gtk2::VBox->new);
687 $vb->add (my $sw = Gtk2::ScrolledWindow->new);
688 $sw->set_policy ('automatic', 'automatic');
689 $sw->add (my $txt = Gtk2::TextView->new);
690 $vb->pack_start (my $hb = Gtk2::HBox->new (1, 1), 0, 1, 0);
691 $hb->pack_start (my $svbtn = Gtk2::Button->new ("save"), 1, 1, 0);
692 $hb->pack_start (my $logbtn = Gtk2::Button->new ("add log"), 1, 1, 0);
693 $hb->pack_start (my $closebtn = Gtk2::Button->new ("close"), 1, 1, 0);
694
695 my $buf = $txt->get_buffer ();
696 $buf->set_text ($self->{map}{map}{info}{msg});
697
698 $svbtn->signal_connect (clicked => sub {
699 my $buf = $txt->get_buffer ();
700 my $txt = $buf->get_text ($buf->get_start_iter, $buf->get_end_iter, 0);
701 $self->{map}{map}{info}{msg} = $txt;
702 });
703
704 $logbtn->signal_connect (clicked => sub {
705 my $buf = $txt->get_buffer ();
706 $buf->insert ($buf->get_start_iter, "- " . strftime ("%F %T %Z", localtime (time)) . " by " . ($main::CFG->{username} || $ENV{USER}) . ":\n");
707 $txt->set_buffer ($buf);
708 });
709
710 $closebtn->signal_connect (clicked => sub {
711 $w->destroy;
712 });
713
714 ::set_pos_and_size ($w, $main::CFG->{map_info}, 400, 400, 220, 20);
715 $w->signal_connect (destroy => sub {
716 delete $self->{map_info};
717 });
718 $w->show_all;
719 }
720
721 sub open_map_prop {
722 my ($self) = @_;
723
724 return if $self->{map_properties};
725
726 my $w = $self->{map_properties} = GCE::HashDialogue->new ();
727
728 $w->init (
729 dialog_default_size => [500, 500, 220, 20],
730 layout_name => 'map_prop_win',
731 title => 'map properties',
732 ref_hash => $self->{map}{map}{info},
733 close_on_save => 1,
734 dialog => [
735 [qw/name Name string/],
736 [qw/region Region string/],
737 [qw/enter_x Enter-x string/],
738 [qw/enter_y Enter-y string/],
739 [qw/reset_timeout Reset-timeout string/],
740 [qw/swap_time Swap-timeout string/],
741 [undef, qw/x sep/],
742 [qw/difficulty Difficulty string/],
743 [qw/windspeed Windspeed string/],
744 [qw/pressure Pressure string/],
745 [qw/humid Humid string/],
746 [qw/temp Temp string/],
747 [qw/darkness Darkness string/],
748 [qw/sky Sky string/],
749 [qw/winddir Winddir string/],
750 [undef, qw/x sep/],
751 [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
752 [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
753 [undef, qw/x sep/],
754 # [qw/msg Text text/],
755 # [qw/maplore Maplore text/],
756 [qw/outdoor Outdoor check/],
757 [qw/unique Unique check/],
758 [qw/fixed_resettime Fixed-resettime check/],
759 [per_player => 'Per player' => 'check'],
760 [per_party => 'Per party' => 'check'],
761 [no_reset => 'No reset' => 'check'],
762 [music => 'Map Music' => 'string'],
763 [undef, qw/x sep/],
764 [qw/tile_path_1 Northpath string/],
765 [qw/tile_path_2 Eastpath string/],
766 [qw/tile_path_3 Southpath string/],
767 [qw/tile_path_4 Westpath string/],
768 [qw/tile_path_5 Toppath string/],
769 [qw/tile_path_6 Bottompath string/],
770 [undef, qw/x sep/],
771 [undef, 'For shop description look in the manual',
772 'button', sub { $::MAINWIN->show_help_window }],
773 [qw/shopmin Shopmin string/],
774 [qw/shopmax Shopmax string/],
775 [qw/shoprace Shoprace string/],
776 [qw/shopgreed Shopgreed string/],
777 [qw/shopitems Shopitems string/],
778 ]
779 );
780
781 $w->signal_connect (destroy => sub { delete $self->{map_properties} });
782 $w->show_all;
783 }
784
785 #################################################################
786 ###### MAP EDITOR INIT ##########################################
787 #################################################################
788
789 sub INIT_INSTANCE {
790 my ($self) = @_;
791
792 $self->set_window_title;
793 $self->add (my $vb = Gtk2::VBox->new);
794
795 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
796
797 $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0);
798
799 $map->signal_connect_after (stack_change => sub {
800 my ($map, $x, $y, $stack) = @_;
801 $self->update_overlays ($x, $y, $stack);
802 });
803 $map->signal_connect_after (swap_stack_change => sub {
804 my ($map, $x, $y, $stack) = @_;
805 $self->update_overlays ($x, $y, $stack);
806 });
807
808 $self->{connection_overlay_foreground}
809 = Gtk2::Gdk::Color->new (257 * 255, 257 * 255, 0);
810 $self->{connection_overlay_background}
811 = Gtk2::Gdk::Color->new (0, 0, 0);
812 # my $ygc
813 # = $self->{connection_overlay_yellow_gc}
814 # = Gtk2::Gdk::GC->new ($self->{map}{window});
815 # $ygc->set_foreground (Gtk2::Gdk::Color->new (257 * 255, 257 * 255, 0));
816
817 $map->signal_connect_after (key_press_event => sub {
818 my ($map, $event) = @_;
819
820 my $kv = $event->keyval;
821
822 my $ret = 0;
823
824 my ($x, $y) = $map->coord ($map->get_pointer);
825 for ([Control_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{erase} }],
826 [Alt_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{pick} }],
827 [c => sub { $::MAINWIN->{edit_collection}{select}->copy }],
828 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
829 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
830 )
831 {
832 my $ed = $_;
833
834 if ($kv == $Gtk2::Gdk::Keysyms{$ed->[0]}) {
835 my $was_in_draw = defined $self->{draw_mode};
836
837 $self->stop_drawmode ($map)
838 if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
839
840 $ed->[1]->();
841 $ret = 1;
842
843 $self->start_drawmode ($map)
844 if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
845 }
846 }
847
848 if ($self->ea->special_arrow) {
849 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
850 } else {
851 # FIXME: Get the original cursor and insert it here
852 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
853 }
854
855 $ret
856 });
857
858 $map->signal_connect_after (key_release_event => sub {
859 my ($map, $event) = @_;
860
861 my $ret = 0;
862
863 if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L}
864 or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L})
865 {
866 my $was_in_draw = defined $self->{draw_mode};
867
868 $self->stop_drawmode ($map)
869 if $was_in_draw;
870
871 delete $self->{ea_alt};
872 $ret = 1;
873
874 $self->start_drawmode ($map)
875 if $was_in_draw;
876 }
877
878 if ($self->ea->special_arrow) {
879 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
880 } else {
881 # FIXME: Get the original cursor and insert it here
882 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
883 }
884
885 $ret
886 });
887 $map->signal_connect_after (button_press_event => sub {
888 my ($map, $event) = @_;
889
890 if ((not $self->{draw_mode}) and $event->button == 1) {
891 my $ea = $self->ea;
892
893 $self->start_drawmode ($map);
894
895 $ea->want_cursor
896 or $map->disable_tooltip;
897
898 return 1;
899 } elsif ($event->button == 3) {
900 $self->do_context_menu ($map, $event);
901 return 1;
902 }
903
904 0
905 });
906
907 $map->signal_connect_after (motion_notify_event => sub {
908 my ($map, $event) = @_;
909
910 $self->{draw_mode}
911 or return;
912
913 my $ea = $self->ea;
914
915 my ($X, $Y) = @{$self->{draw_mode}}[0,1];
916 my ($x, $y) = $map->coord ($map->get_pointer);
917
918 while ($x != $X || $y != $Y) {
919
920 $X++ if $X < $x;
921 $X-- if $X > $x;
922 $Y++ if $Y < $y;
923 $Y-- if $Y > $y;
924
925 unless ($ea->only_on_click) {
926 $ea->edit ($map, $X, $Y, $self)
927 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
928 }
929 }
930
931 @{$self->{draw_mode}}[0,1] = ($X, $Y);
932
933 1
934 });
935
936 $map->signal_connect_after (button_release_event => sub {
937 my ($map, $event) = @_;
938
939 if ($self->{draw_mode} and $event->button == 1) {
940 my $ea = $self->ea;
941
942 $self->stop_drawmode ($map);
943
944 $ea->want_cursor
945 or $map->enable_tooltip;
946
947 return 1;
948 }
949
950 0
951 });
952
953 ::set_pos_and_size ($self, $main::CFG->{map_window}, 500, 500, 200, 0);
954 }
955
956 =head1 AUTHOR
957
958 Marc Lehmann <schmorp@schmorp.de>
959 http://home.schmorp.de/
960
961 Robin Redeker <elmex@ta-sa.org>
962 http://www.ta-sa.org/
963
964 =cut
965
966 1
967