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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines