ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.73.2.1
Committed: Thu Jul 2 11:34:19 2009 UTC (14 years, 11 months ago) by elmex
Branch: cursor
Changes since 1.73: +82 -0 lines
Log Message:
added preliminary cursor code.

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 Deliantra;
14 use Deliantra::Map;
15 use Deliantra::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 Scalar::Util qw/weaken/;
27 use Storable qw/dclone/;
28
29 use Guard;
30
31 use strict;
32
33 #################################################################
34 ###### WINDOW MANAGEMENT ########################################
35 #################################################################
36
37 sub save_layout {
38 my ($self) = @_;
39
40 $self->{attach_editor}->save_layout if $self->{attach_editor};
41 $self->{map_properties}->save_layout if $self->{map_properties};
42 $self->{meta_info_win}->save_layout if $self->{meta_info_win};
43
44 $main::CFG->{map_info} = main::get_pos_and_size ($self->{map_info})
45 if $self->{map_info};
46 }
47
48 sub close_windows {
49 my ($self) = @_;
50
51 $self->{attach_editor}->destroy if $self->{attach_editor};
52 $self->{map_properties}->destroy if $self->{map_properties};
53 $self->{meta_info_win}->destroy if $self->{meta_info_win};
54 }
55
56 #################################################################
57 ###### MENU MANAGEMENT ##########################################
58 #################################################################
59
60 sub do_context_menu {
61 my ($self, $map, $event) = @_;
62
63 my ($x, $y) = $map->coord ($event->x, $event->y);
64
65 my $menu = Gtk2::Menu->new;
66 foreach my $cm (
67 [
68 Follow => sub {
69 $::MAINWIN->{edit_collection}{followexit}->edit ($map, $x, $y, $self)
70 },
71 ]
72 ) {
73 my $item = Gtk2::MenuItem->new ($cm->[0]);
74 $menu->append ($item);
75 $item->show;
76 $item->signal_connect (activate => $cm->[1]);
77 }
78
79 $menu->append (my $sep = new Gtk2::SeparatorMenuItem);
80 $sep->show;
81
82 for my $sr (reverse $self->get_stack_refs ($map, $x, $y)) {
83 my $item = Gtk2::MenuItem->new ($sr->longname);
84 $menu->append ($item);
85 $item->set_submenu (my $smenu = new Gtk2::Menu);
86
87 for my $act (
88 [ 'Add inventory' => sub { $_[0]->add_inv ($::MAINWIN->get_pick) } ],
89 [ 'Find in picker' => sub { $::MAINWIN->open_pick_window ({ selection => $sr->picker_folder }) } ],
90 ) {
91 my $sitem = Gtk2::MenuItem->new ($act->[0]);
92 $smenu->append ($sitem);
93 $sitem->signal_connect (activate => sub { $act->[1]->($sr) });
94 $sitem->show;
95 }
96
97 $item->show;
98 }
99
100 $menu->popup (undef, undef, undef, undef, $event->button, $event->time);
101 }
102
103 sub build_menu {
104 my ($self) = @_;
105
106 my $menu_tree = [
107 _File => {
108 item_type => '<Branch>',
109 children => [
110 "_Save" => {
111 callback => sub { $self->save_map },
112 accelerator => '<ctrl>S'
113 },
114 "Save As" => {
115 callback => sub { $self->save_map_as },
116 },
117 "Map _Info" => {
118 callback => sub { $self->open_map_info },
119 accelerator => "<ctrl>I",
120 },
121 "Map _Properties" => {
122 callback => sub { $self->open_map_prop },
123 accelerator => "<ctrl>P"
124 },
125 "Map _Attachments" => {
126 callback => sub { $self->open_attach_edit },
127 accelerator => "<ctrl>A"
128 },
129 # "Map Meta _Info" => {
130 # callback => sub { $self->open_meta_info },
131 # },
132 # Upload => {
133 # item_type => '<Branch>',
134 # children => [
135 # "Upload for testing" => {
136 # callback => sub { $self->upload_map_test },
137 # },
138 # "Upload for inclusion" => {
139 # callback => sub { $self->upload_map_incl },
140 # },
141 # ]
142 # },
143 "_Map Resize" => {
144 callback => sub { $self->open_resize_map },
145 },
146 "Close" => {
147 callback => sub { $self->destroy },
148 accelerator => "<ctrl>W",
149 },
150 ]
151 },
152 _Edit => {
153 item_type => '<Branch>',
154 children => [
155 "_Undo" => {
156 callback => sub { $self->undo },
157 accelerator => "<ctrl>Z"
158 },
159 "_Redo" => {
160 callback => sub { $self->redo },
161 accelerator => "<ctrl>Y"
162 },
163 ]
164 },
165 _Go => {
166 item_type => '<Branch>',
167 children => [
168 "_Up" => {
169 callback => sub { $self->follow ('u') },
170 accelerator => "<ctrl>Up"
171 },
172 "_Down" => {
173 callback => sub { $self->follow ('d') },
174 accelerator => "<ctrl>Down"
175 },
176 "_Right" => {
177 callback => sub { $self->follow ('r') },
178 accelerator => "<ctrl>Right"
179 },
180 "_Left" => {
181 callback => sub { $self->follow ('l') },
182 accelerator => "<ctrl>Left"
183 },
184 ]
185 },
186 _Help => {
187 item_type => '<Branch>',
188 children => [
189 _Manual => {
190 callback => sub { $::MAINWIN->show_help_window },
191 accelerator => "<ctrl>H"
192 },
193 ]
194 },
195 ];
196
197 my $men =
198 Gtk2::SimpleMenu->new (
199 menu_tree => $menu_tree,
200 default_callback => \&default_cb,
201 );
202
203 for (
204 [i => 'pick'],
205 [p => 'place'],
206 [e => 'erase'],
207 [s => 'select'],
208 [l => 'eval'],
209 [t => 'connect'],
210 [f => 'followexit']
211 )
212 {
213 my $tool = $_->[1];
214 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
215 sub { $::MAINWIN->set_edit_tool ($tool) });
216 }
217
218 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{'r'}, ['control-mask'], 'visible',
219 sub { $self->redo });
220
221 $self->add_accel_group ($men->{accel_group});
222
223 return $men->{widget};
224 }
225
226 #################################################################
227 ###### EDIT TOOL STUFF ##########################################
228 #################################################################
229
230 sub set_edit_tool {
231 my ($self, $tool) = @_;
232
233 $self->{etool} = $tool;
234
235 if ($self->ea->special_arrow) {
236 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
237 } else {
238 # FIXME: Get the original cursor and insert it here
239 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
240 }
241 }
242
243 sub ea {
244 my ($self) = @_;
245 $self->{ea_alt} || $self->{etool};
246 }
247
248 sub start_drawmode {
249 my ($self, $map) = @_;
250
251 $self->{draw_mode} and return;
252
253 # XXX: is this okay? my ($x, $y) = $map->coord ($event->x, $event->y);
254 my ($x, $y) = $map->coord ($map->get_pointer);
255
256 my $ea = $self->ea;
257
258 # XXX: ######################################XXX XXX XXX XXX XXX XXX XXX XXX XXX
259 $self->{_tc1} = $self->cursor ('edit' => $x, $y);
260
261 $ea->begin ($map, $x, $y, $self);
262
263 $ea->edit ($map, $x, $y, $self)
264 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
265
266 $self->{draw_mode} = [$x, $y];
267 }
268
269 sub stop_drawmode {
270 my ($self, $map) = @_;
271
272 $self->{draw_mode} or return;
273
274 my ($x, $y) = $map->coord ($map->get_pointer);
275
276 my $ea = $self->ea;
277 $ea->end ($map, $x, $y, $self);
278
279 delete $self->{draw_mode};
280 }
281
282 #################################################################
283 ###### UTILITY FUNCTIONS ########################################
284 #################################################################
285
286 sub follow {
287 my ($self, $dir) = @_;
288
289 my %dir_to_path = (
290 u => 'tile_path_1',
291 d => 'tile_path_3',
292 r => 'tile_path_2',
293 l => 'tile_path_4',
294 );
295
296 defined $dir_to_path{$dir}
297 or return;
298 my $map = $self->{map}{map}{info}{$dir_to_path{$dir}}
299 or return;
300
301 $map = map2abs ($map, $self);
302 $::MAINWIN->open_map_editor ($map);
303 }
304
305 # FIXME: Fix the automatic update of the attribute editor! and also the stack view!
306 sub undo {
307 my ($self) = @_;
308
309 my $map = $self->{map}; # the Deliantra::MapWidget
310
311 $map->{undo_stack_pos}
312 or return;
313
314 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
315
316 $::MAINWIN->update_stack_view ();
317 }
318
319 sub get_stack_refs {
320 my ($self, $map, $x, $y) = @_;
321
322 my $cstack = $map->get ($x, $y);
323
324 return [] unless @$cstack;
325
326 my @refs;
327
328 for my $arch (@$cstack) {
329 my ($ix, $iy, $iarch, $istack) = devirtualize ($map, $x, $y, $arch, $cstack);
330 push @refs,
331 GCE::ArchRef->new (
332 arch => $iarch,
333 source => 'map',
334 cb => sub {
335 $map->change_begin ('attredit');
336 $map->change_stack ($ix, $iy, $istack);
337
338 if (my $changeset = $map->change_end) {
339 splice @{ $map->{undo_stack} ||= [] },
340 $map->{undo_stack_pos}++, 1e6,
341 $changeset;
342 }
343 }
344 );
345 }
346
347 return @refs;
348 }
349
350 sub redo {
351 my ($self) = @_;
352
353 my $map = $self->{map}; # the Deliantra::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
362 sub load_meta_info {
363 my ($mapfile) = @_;
364 if (-e "$mapfile.meta") {
365 open my $metafh, "<", "$mapfile.meta"
366 or warn "Couldn't open meta file $mapfile.meta: $!";
367 my $metadata = do { local $/; <$metafh> };
368 return Deliantra::decode_json ($metadata);
369 }
370 }
371
372 sub save_meta_info {
373 my ($mapfile, $metainfo) = @_;
374 open my $metafh, ">", "$mapfile.meta"
375 or warn "Couldn't write meta file $mapfile.meta: $!";
376 print $metafh Deliantra::encode_json ($metainfo);
377 }
378
379 sub open_map {
380 my ($self, $path, $key) = @_;
381
382 $self->{mapkey} = $key;
383
384 if (ref $path) {
385 $self->{map}->set_map ($path);
386 delete $self->{meta_info};
387 $self->set_window_title;
388
389 } else {
390 my $ok = 0;
391 if (-e $path && -f $path) {
392 $ok = 1;
393 } else {
394 unless ($path =~ m/\.map$/) { # yuck
395 my $p = $path . '.map';
396 if ($ok = -e $p && -f $p) {
397 $path = $p;
398 }
399 }
400 }
401 unless ($ok) {
402 die "Couldn't open '$path' or find '$path.map': No such file or it is not a file.\n";
403 }
404 $self->{path} = $path;
405 $self->{map}->set_map (my $m = new_from_file Deliantra::Map $path);
406 $self->{meta_info} = load_meta_info ($path);
407 $self->set_window_title ($self->{path});
408 $::MAINWIN->add_recent($path);
409 }
410
411 $self->update_overlays;
412 $self->close_windows;
413 }
414
415 sub save_map {
416 my ($self) = @_;
417
418 if ($self->{path}) {
419 $self->{map}{map}->write_file ($self->{path});
420 if ($self->{meta_info}) {
421 save_meta_info ($self->{path}, $self->{meta_info});
422 }
423 quick_msg ($self, "saved to $self->{path}");
424 $self->set_window_title ($self->{path});
425 $::MAINWIN->add_recent($self->{path});
426 } else {
427 $self->save_map_as;
428 }
429 }
430
431 sub set_window_title {
432 my ($self, $title) = @_;
433
434 $title = 'Unsaved'
435 unless $title;
436
437 $self->set_title(File::Basename::basename($title).' - deliantra editor');
438 }
439
440 sub save_map_as {
441 my ($self) = @_;
442
443 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
444
445 if ('ok' eq $fc->run) {
446
447 $::MAINWIN->{fc_last_folder} = $fc->get_current_folder;
448 $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++;
449
450 if($fc->get_filename) {
451 $self->{path} = $fc->get_filename;
452 $self->save_map;
453 }
454 }
455
456 $fc->destroy;
457 }
458
459 sub _get_conns_for_obj {
460 my ($obj, $x, $y, $rconns) = @_;
461
462 if (defined $obj->{connected}) {
463 $rconns->{$x}->{$y}->{$obj->{connected}} = 1;
464
465 } elsif (defined $obj->{msg}) {
466 my $msg = $obj->{msg};
467
468 while ($msg =~ s/\@trigger\s+(\d+)//) {
469 $rconns->{$x}->{$y}->{$1} = 1;
470 }
471 }
472 }
473
474 sub update_overlays {
475 my ($self, $sx, $sy, $stack) = @_;
476 my $conns = {};
477
478 if (not $stack) {
479 my ($w, $h) = ($self->{map}{map}{width}, $self->{map}{map}{height});
480
481 for (my $x = 0; $x < $w; $x++) {
482 for (my $y = 0; $y < $h; $y++) {
483 _get_conns_for_obj ($_, $x, $y, $conns)
484 for @{$self->{map}->get_ro ($x, $y)};
485 }
486 }
487
488 # delete prev. overlays
489 for (keys %{$self->{_conn_overlays}}) {
490 $self->{map}->overlay ($_);
491 }
492 } else {
493 # del old overlay for this place
494 my $ovl = "connection_$sx\_$sy";
495 $self->{map}->overlay ($ovl) if delete $self->{_conn_overlays}->{$ovl};
496 _get_conns_for_obj ($_, $sx, $sy, $conns)
497 for @$stack;
498 }
499
500 # put new overlays there
501 for my $x (keys %$conns) {
502 for my $y (keys %{$conns->{$x}}) {
503 my $ovlname = "connection_$x\_$y";
504 my $conns_ovl = join (', ', keys %{$conns->{$x}->{$y}});
505 $self->{_conn_overlays}->{$ovlname} = 1;
506 my ($a, $t, $ac)
507 = Gtk2::Pango->parse_markup (
508 "<span size=\"xx-small\">$conns_ovl</span>", ''
509 );
510 my $pl = $self->{map}->create_pango_layout ('');
511 $pl->set_attributes ($a);
512 $pl->set_text ($t);
513 my ($ink_rect, $logical_rect) = $pl->get_pixel_extents;
514
515 $self->{map}->overlay (
516 $ovlname, $x * TILESIZE, $y * TILESIZE, TILESIZE, TILESIZE,
517 sub {
518 my ($mapwin, $x, $y) = @_;
519 if (!$self->{_conn_upd_ovl_gc_fg}) {
520 my $gc
521 = $self->{_conn_upd_ovl_gc_fg}
522 = Gtk2::Gdk::GC->new ($mapwin->{window});
523 my $cm = $mapwin->{window}->get_colormap;
524 $gc->set_foreground (gtk2_get_color ($mapwin, "yellow"));
525 $gc->set_background (gtk2_get_color ($mapwin, "black"));
526 }
527 $mapwin->{window}->draw_rectangle (
528 $mapwin->style->black_gc,
529 1,
530 $x, $y, $logical_rect->{width} + 2, $logical_rect->{height} + 2,
531 );
532 $mapwin->{window}->draw_rectangle (
533 $self->{_conn_upd_ovl_gc_fg},
534 0,
535 $x, $y, $logical_rect->{width} + 2, $logical_rect->{height} + 2,
536 );
537 $mapwin->{window}->draw_layout_with_colors (
538 $mapwin->style->black_gc, $x + 1, $y + 1, $pl,
539 $self->{connection_overlay_foreground},
540 $self->{connection_overlay_background},
541 )
542 }
543 );
544 }
545 }
546 }
547
548 #################################################################
549 ###### DIALOGOUES ###############################################
550 #################################################################
551
552 sub open_resize_map {
553 my ($self) = @_;
554
555 return if $self->{meta_info_win};
556
557 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
558
559 $w->init (
560 dialog_default_size => [500, 200, 220, 20],
561 layout_name => 'resize_win',
562 title => 'resize map',
563 ref_hash => $self->{map}{map}{info},
564 dialog => [
565 [width => 'Width' => 'string'],
566 [height => 'Height' => 'string'],
567 ],
568 close_on_save => 1,
569 save_button_label => 'resize',
570 save_cb => sub {
571 my ($info) = @_;
572 $self->{map}{map}->resize ($info->{width}, $info->{height});
573 $self->{map}->set_map ($self->{map}{map});
574 $self->update_overlays;
575 }
576 );
577
578 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
579
580 $w->show_all;
581 }
582
583 sub open_attach_edit {
584 my ($self) = @_;
585
586 my $w = GCE::AttachEditor->new;
587 $w->set_attachment (
588 $self->{map}{map}{info}{attach},
589 sub {
590 if (@{$_[0]}) {
591 $self->{map}{map}{info}{attach} = $_[0]
592 } else {
593 delete $self->{map}{map}{info}{attach};
594 }
595 }
596 );
597 $self->{attach_editor} = $w;
598 $w->signal_connect (destroy => sub { delete $self->{attach_editor} });
599 $w->show_all;
600 }
601
602 sub upload_map_incl {
603 my ($self) = @_;
604
605 my $meta = dclone $self->{meta_info};
606
607 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
608
609 $w->init (
610 dialog_default_size => [500, 300, 220, 20],
611 layout_name => 'map_upload_incl',
612 title => 'gce - map inclusion upload',
613 ref_hash => $meta,
614 text_entry => { key => 'changes', label => 'Changes (required for inclusion):' },
615 dialog => [
616 [gameserver => 'Game server' => 'label'],
617 [testserver => 'Test server' => 'label'],
618 [undef => x => 'sep' ],
619 [cf_login => 'Server login name' => 'string'],
620 [cf_password=> 'Password' => 'password'],
621 [path => 'Map path' => 'string'],
622 ],
623 close_on_save => 1,
624 save_cb => sub {
625 my ($meta) = @_;
626 warn "UPLOAD[".Deliantra::encode_json ($meta)."]\n";
627 }
628 );
629
630 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
631
632 $w->show_all;
633 }
634
635 sub upload_map_test {
636 my ($self) = @_;
637
638 my $meta = dclone $self->{meta_info};
639
640 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
641
642 $w->init (
643 dialog_default_size => [500, 300, 220, 20],
644 layout_name => 'map_upload_test',
645 title => 'gce - map test upload',
646 ref_hash => $meta,
647 dialog => [
648 [gameserver => 'Game server' => 'string'],
649 [testserver => 'Test server' => 'string'],
650 [undef => x => 'sep' ],
651 [cf_login => 'Server login name' => 'string'],
652 [cf_password=> 'Password' => 'password'],
653 [path => 'Map path' => 'string'],
654 ],
655 save_cb => sub {
656 my ($meta) = @_;
657 warn "UPLOAD[".Deliantra::encode_json ($meta)."]\n";
658 }
659 );
660
661 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
662
663 $w->show_all;
664
665
666 }
667
668 sub open_meta_info {
669 my ($self) = @_;
670
671 return if $self->{meta_info_win};
672
673 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
674
675 $w->init (
676 dialog_default_size => [500, 300, 220, 20],
677 layout_name => 'meta_info_win',
678 title => 'meta info',
679 ref_hash => $self->{meta_info},
680 dialog => [
681 [path => 'Map path' => 'string'],
682 [cf_login => 'Login name' => 'string'],
683 [revision => 'CVS Revision' => 'label'],
684 [cvs_root => 'CVS Root' => 'label'],
685 [lib_root => 'LIB Root' => 'label'],
686 [testserver => 'Test server' => 'label'],
687 [gameserver => 'Game server' => 'label'],
688 ],
689 );
690
691 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
692
693 $w->show_all;
694 }
695
696 sub open_map_info {
697 my ($self) = @_;
698 return if $self->{map_info};
699
700 my $w = $self->{map_info} = Gtk2::Window->new ('toplevel');
701 $w->set_title ("deliantra editor - map info");
702
703 $w->add (my $vb = Gtk2::VBox->new);
704 $vb->add (my $sw = Gtk2::ScrolledWindow->new);
705 $sw->set_policy ('automatic', 'automatic');
706 $sw->add (my $txt = Gtk2::TextView->new);
707 $vb->pack_start (my $hb = Gtk2::HBox->new (1, 1), 0, 1, 0);
708 $hb->pack_start (my $svbtn = Gtk2::Button->new ("save"), 1, 1, 0);
709 $hb->pack_start (my $logbtn = Gtk2::Button->new ("add log"), 1, 1, 0);
710 $hb->pack_start (my $closebtn = Gtk2::Button->new ("close"), 1, 1, 0);
711
712 my $buf = $txt->get_buffer ();
713 $buf->set_text ($self->{map}{map}{info}{msg});
714
715 $svbtn->signal_connect (clicked => sub {
716 my $buf = $txt->get_buffer ();
717 my $txt = $buf->get_text ($buf->get_start_iter, $buf->get_end_iter, 0);
718 $self->{map}{map}{info}{msg} = $txt;
719 });
720
721 $logbtn->signal_connect (clicked => sub {
722 my $buf = $txt->get_buffer ();
723 $buf->insert ($buf->get_start_iter, "- " . strftime ("%F %T %Z", localtime (time)) . " by " . ($main::CFG->{username} || $ENV{USER}) . ":\n");
724 $txt->set_buffer ($buf);
725 });
726
727 $closebtn->signal_connect (clicked => sub {
728 $w->destroy;
729 });
730
731 ::set_pos_and_size ($w, $main::CFG->{map_info}, 400, 400, 220, 20);
732 $w->signal_connect (destroy => sub {
733 delete $self->{map_info};
734 });
735 $w->show_all;
736 }
737
738 sub open_map_prop {
739 my ($self) = @_;
740
741 return if $self->{map_properties};
742
743 my $w = $self->{map_properties} = GCE::HashDialogue->new ();
744
745 $w->init (
746 dialog_default_size => [500, 500, 220, 20],
747 layout_name => 'map_prop_win',
748 title => 'map properties',
749 ref_hash => $self->{map}{map}{info},
750 close_on_save => 1,
751 dialog => [
752 [qw/name Name string/],
753 [qw/region Region string/],
754 [qw/enter_x Enter-x string/],
755 [qw/enter_y Enter-y string/],
756 [qw/reset_timeout Reset-timeout string/],
757 [qw/swap_time Swap-timeout string/],
758 [undef, qw/x sep/],
759 [qw/difficulty Difficulty string/],
760 [qw/windspeed Windspeed string/],
761 [qw/pressure Pressure string/],
762 [qw/humid Humid string/],
763 [qw/temp Temp string/],
764 [qw/darkness Darkness string/],
765 [qw/sky Sky string/],
766 [qw/winddir Winddir string/],
767 [undef, qw/x sep/],
768 [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
769 [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
770 [undef, qw/x sep/],
771 # [qw/msg Text text/],
772 # [qw/maplore Maplore text/],
773 [qw/outdoor Outdoor check/],
774 [qw/unique Unique check/],
775 [qw/fixed_resettime Fixed-resettime check/],
776 [per_player => 'Per player' => 'check'],
777 [per_party => 'Per party' => 'check'],
778 [no_reset => 'No reset' => 'check'],
779 [music => 'Map Music' => 'string'],
780 [undef, qw/x sep/],
781 [qw/tile_path_1 Northpath string/],
782 [qw/tile_path_2 Eastpath string/],
783 [qw/tile_path_3 Southpath string/],
784 [qw/tile_path_4 Westpath string/],
785 [qw/tile_path_5 Toppath string/],
786 [qw/tile_path_6 Bottompath string/],
787 [undef, qw/x sep/],
788 [undef, 'For shop description look in the manual',
789 'button', sub { $::MAINWIN->show_help_window }],
790 [qw/shopmin Shopmin string/],
791 [qw/shopmax Shopmax string/],
792 [qw/shoprace Shoprace string/],
793 [qw/shopgreed Shopgreed string/],
794 [qw/shopitems Shopitems string/],
795 ]
796 );
797
798 $w->signal_connect (destroy => sub { delete $self->{map_properties} });
799 $w->show_all;
800 }
801
802 #################################################################
803 ###### CURSORS ##################################################
804 #################################################################
805
806 sub cursor {
807 my ($self, $owner, $x, $y, $z, @inv_path) = @_;
808
809 my $cursmap = ($self->{_cursors} ||= []);
810 my $cursor;
811
812 push @{$cursmap->[$x]->[$y]}, $cursor = {
813 x => $x,
814 y => $y,
815 owner => $owner
816 };
817
818 weaken $self;
819 weaken $cursmap->[$x]->[$y]->[-1];
820
821 $cursor->{z} = $z if defined $z;
822 $cursor->{inv} = \@inv_path if @inv_path;
823
824 my ($cx, $cy, $cid) = ($cursor->{x}, $cursor->{y}, "$cursor");
825 $cursor->{guard} = guard {
826 $self->remove_cursor ($cx, $cy, $cid) if defined $self
827 };
828
829 $self->update_cursor_overlay ($cursor);
830 $cursor
831 }
832
833 sub update_cursor_overlay {
834 my ($self, $cursor) = @_;
835
836 if ($cursor->{owner} eq 'edit') {
837 $self->{map}->overlay (attr_edit =>
838 $cursor->{x} * TILESIZE,
839 $cursor->{y} * TILESIZE,
840 TILESIZE,
841 TILESIZE,
842 sub {
843 my ($self, $x, $y) = @_;
844
845 if (!$self->{_conn_upd_curs_gc_fg}) {
846 my $gc
847 = $self->{_conn_upd_curs_gc_fg}
848 = Gtk2::Gdk::GC->new ($self->{window});
849 my $cm = $self->{window}->get_colormap;
850 $gc->set_foreground (gtk2_get_color ($self, "green"));
851 $gc->set_background (gtk2_get_color ($self, "black"));
852 }
853
854 $self->{window}->draw_rectangle (
855 $_ & 1 ? $self->style->black_gc : $self->{_conn_upd_curs_gc_fg},
856 0,
857 $x + $_, $y + $_,
858 TILESIZE - 1 - $_ * 2,
859 TILESIZE - 1 - $_ * 2
860 ) for 0..3;
861 }
862 );
863 }
864 }
865
866 sub remove_cursor {
867 my ($self, $x, $y, $cid) = @_;
868
869 my $cursmap = ($self->{_cursors} ||= []);
870 @{$cursmap->[$x]->[$y]} = grep { $_ ne $cid } @{$cursmap->[$x]->[$y]};
871
872 for (my $i = 0; $i < scalar (@{$cursmap->[$x]->[$y]}); $i++) {
873 weaken $cursmap->[$x]->[$y]->[$i];
874 }
875 }
876
877 #################################################################
878 ###### MAP EDITOR INIT ##########################################
879 #################################################################
880
881 sub INIT_INSTANCE {
882 my ($self) = @_;
883
884 $self->set_window_title;
885 $self->add (my $vb = Gtk2::VBox->new);
886
887 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
888
889 $vb->pack_start (my $map = $self->{map} = Deliantra::MapWidget->new, 1, 1, 0);
890
891 $map->signal_connect_after (stack_change => sub {
892 my ($map, $x, $y, $stack) = @_;
893 $self->update_overlays ($x, $y, $stack);
894 $::MAINWIN->update_map_pos ($self, $x, $y);
895 });
896 $map->signal_connect_after (swap_stack_change => sub {
897 my ($map, $x, $y, $stack) = @_;
898 $self->update_overlays ($x, $y, $stack);
899 $::MAINWIN->update_map_pos ($self, $x, $y);
900 });
901
902 $self->{connection_overlay_foreground}
903 = Gtk2::Gdk::Color->new (257 * 255, 257 * 255, 0);
904 $self->{connection_overlay_background}
905 = Gtk2::Gdk::Color->new (0, 0, 0);
906 # my $ygc
907 # = $self->{connection_overlay_yellow_gc}
908 # = Gtk2::Gdk::GC->new ($self->{map}{window});
909 # $ygc->set_foreground (Gtk2::Gdk::Color->new (257 * 255, 257 * 255, 0));
910
911 $map->signal_connect_after (key_press_event => sub {
912 my ($map, $event) = @_;
913
914 my $kv = $event->keyval;
915
916 my $ret = 0;
917
918 my ($x, $y) = $map->coord ($map->get_pointer);
919 for ([c => sub { $::MAINWIN->{edit_collection}{select}->copy }],
920 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
921 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
922 )
923 {
924 my $ed = $_;
925
926 if ($kv == $Gtk2::Gdk::Keysyms{$ed->[0]}) {
927 my $was_in_draw = defined $self->{draw_mode};
928
929 $self->stop_drawmode ($map) if $was_in_draw;
930
931 $ed->[1]->();
932 $ret = 1;
933
934 $self->start_drawmode ($map) if $was_in_draw;
935 }
936 }
937
938 if ($self->ea->special_arrow) {
939 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
940 } else {
941 # FIXME: Get the original cursor and insert it here
942 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
943 }
944
945 $ret
946 });
947
948 $map->signal_connect_after (button_press_event => sub {
949 my ($map, $event) = @_;
950
951 if ((not $self->{draw_mode}) and $event->button == 1) {
952 my $ea = $self->ea;
953
954 $self->start_drawmode ($map);
955
956 $ea->want_cursor
957 or $map->disable_tooltip;
958
959 return 1;
960 } elsif ($event->button == 3) {
961 $self->do_context_menu ($map, $event);
962 return 1;
963 }
964
965 0
966 });
967
968 $map->signal_connect_after (motion_notify_event => sub {
969 my ($map, $event) = @_;
970
971 $self->{draw_mode}
972 or return;
973
974 my $ea = $self->ea;
975
976 my ($X, $Y) = @{$self->{draw_mode}}[0,1];
977 my ($x, $y) = $map->coord ($map->get_pointer);
978
979 while ($x != $X || $y != $Y) {
980
981 $X++ if $X < $x;
982 $X-- if $X > $x;
983 $Y++ if $Y < $y;
984 $Y-- if $Y > $y;
985
986 unless ($ea->only_on_click) {
987 $ea->edit ($map, $X, $Y, $self)
988 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
989 }
990 }
991
992 @{$self->{draw_mode}}[0,1] = ($X, $Y);
993
994 1
995 });
996
997 $map->signal_connect_after (button_release_event => sub {
998 my ($map, $event) = @_;
999
1000 if ($self->{draw_mode} and $event->button == 1) {
1001 my $ea = $self->ea;
1002
1003 $self->stop_drawmode ($map);
1004
1005 $ea->want_cursor
1006 or $map->enable_tooltip;
1007
1008 return 1;
1009 }
1010
1011 0
1012 });
1013
1014 ::set_pos_and_size ($self, $main::CFG->{map_window}, 500, 500, 200, 0);
1015 }
1016
1017 =head1 AUTHOR
1018
1019 Marc Lehmann <schmorp@schmorp.de>
1020 http://home.schmorp.de/
1021
1022 Robin Redeker <elmex@ta-sa.org>
1023 http://www.ta-sa.org/
1024
1025 =cut
1026
1027 1
1028