ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.45
Committed: Tue Nov 28 16:26:22 2006 UTC (17 years, 6 months ago) by elmex
Branch: MAIN
Changes since 1.44: +77 -4 lines
Log Message:
removed some debugging output.
and some changes on HashDialog.

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 Glib::Object::Subclass
22 Gtk2::Window;
23
24 use Storable qw/dclone/;
25
26 use strict;
27
28 #################################################################
29 ###### WINDOW MANAGEMENT ########################################
30 #################################################################
31
32 sub save_layout {
33 my ($self) = @_;
34
35 $self->{attach_editor}->save_layout if $self->{attach_editor};
36 $self->{map_properties}->save_layout if $self->{map_properties};
37 $self->{meta_info_win}->save_layout if $self->{meta_info_win};
38 }
39
40 sub close_windows {
41 my ($self) = @_;
42
43 $self->{attach_editor}->destroy if $self->{attach_editor};
44 $self->{map_properties}->destroy if $self->{map_properties};
45 $self->{meta_info_win}->destroy if $self->{meta_info_win};
46 }
47
48 #################################################################
49 ###### MENU MANAGEMENT ##########################################
50 #################################################################
51
52 sub do_context_menu {
53 my ($self, $map, $event) = @_;
54
55 my ($x, $y) = $map->coord ($event->x, $event->y);
56
57 my $menu = Gtk2::Menu->new;
58 foreach my $cm (
59 [
60 Follow => sub {
61 $::MAINWIN->{edit_collection}{followexit}->edit ($map, $x, $y, $self)
62 },
63 ]
64 ) {
65 my $item = Gtk2::MenuItem->new ($cm->[0]);
66 $menu->append ($item);
67 $item->show;
68 $item->signal_connect (activate => $cm->[1]);
69 }
70
71 my $inv_item = Gtk2::MenuItem->new ("Add inventory");
72 $inv_item->set_submenu (my $smenu = new Gtk2::Menu);
73 $menu->append ($inv_item);
74
75 for my $sr (reverse $self->get_stack_refs ($map, $x, $y)) {
76 my $item = Gtk2::MenuItem->new ($sr->longname);
77 $smenu->append ($item);
78 $item->show;
79 $item->signal_connect (activate => sub {
80 $sr->add_inv ($::MAINWIN->get_pick);
81 });
82 }
83
84 $inv_item->show;
85 $menu->popup (undef, undef, undef, undef, $event->button, $event->time);
86 }
87
88 sub build_menu {
89 my ($self) = @_;
90
91 my $menu_tree = [
92 _File => {
93 item_type => '<Branch>',
94 children => [
95 "_Save" => {
96 callback => sub { $self->save_map },
97 accelerator => '<ctrl>S'
98 },
99 "Save As" => {
100 callback => sub { $self->save_map_as },
101 },
102 "Map _Properties" => {
103 callback => sub { $self->open_map_prop },
104 accelerator => "<ctrl>P"
105 },
106 "Map _Attachments" => {
107 callback => sub { $self->open_attach_edit },
108 accelerator => "<ctrl>A"
109 },
110 "Map Meta _Info" => {
111 callback => sub { $self->open_meta_info },
112 },
113 Upload => {
114 item_type => '<Branch>',
115 children => [
116 "Upload for testing" => {
117 callback => sub { $self->upload_map_test },
118 },
119 "Upload for inclusion" => {
120 callback => sub { $self->upload_map_incl },
121 },
122 ]
123 },
124 "_Map Resize" => {
125 callback => sub { $self->open_resize_map },
126 },
127 "Close" => {
128 callback => sub { $self->destroy },
129 },
130 ]
131 },
132 _Edit => {
133 item_type => '<Branch>',
134 children => [
135 "_Undo" => {
136 callback => sub { $self->undo },
137 accelerator => "<ctrl>Z"
138 },
139 "_Redo" => {
140 callback => sub { $self->redo },
141 accelerator => "<ctrl>Y"
142 },
143 ]
144 },
145 _Go => {
146 item_type => '<Branch>',
147 children => [
148 "_Up" => {
149 callback => sub { $self->follow ('u') },
150 accelerator => "<ctrl>Up"
151 },
152 "_Down" => {
153 callback => sub { $self->follow ('d') },
154 accelerator => "<ctrl>Down"
155 },
156 "_Right" => {
157 callback => sub { $self->follow ('r') },
158 accelerator => "<ctrl>Right"
159 },
160 "_Left" => {
161 callback => sub { $self->follow ('l') },
162 accelerator => "<ctrl>Left"
163 },
164 ]
165 },
166 _Help => {
167 item_type => '<Branch>',
168 children => [
169 _Manual => {
170 callback => sub { $::MAINWIN->show_help_window },
171 accelerator => "<ctrl>H"
172 },
173 ]
174 },
175 ];
176
177 my $men =
178 Gtk2::SimpleMenu->new (
179 menu_tree => $menu_tree,
180 default_callback => \&default_cb,
181 );
182
183 for (
184 [i => 'pick'],
185 [p => 'place'],
186 [e => 'erase'],
187 [s => 'select'],
188 [l => 'eval'],
189 [t => 'connect'],
190 [f => 'followexit']
191 )
192 {
193 my $tool = $_->[1];
194 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
195 sub { $::MAINWIN->set_edit_tool ($tool) });
196 }
197
198 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{'r'}, ['control-mask'], 'visible',
199 sub { $self->redo });
200
201 $self->add_accel_group ($men->{accel_group});
202
203 return $men->{widget};
204 }
205
206 #################################################################
207 ###### EDIT TOOL STUFF ##########################################
208 #################################################################
209
210 sub set_edit_tool {
211 my ($self, $tool) = @_;
212
213 $self->{etool} = $tool;
214
215 if ($self->ea->special_arrow) {
216 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
217 } else {
218 # FIXME: Get the original cursor and insert it here
219 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
220 }
221 }
222
223 sub ea {
224 my ($self) = @_;
225 $self->{ea_alt} || $self->{etool};
226 }
227
228 sub start_drawmode {
229 my ($self, $map) = @_;
230
231 $self->{draw_mode} and return;
232
233 # XXX: is this okay? my ($x, $y) = $map->coord ($event->x, $event->y);
234 my ($x, $y) = $map->coord ($map->get_pointer);
235
236 my $ea = $self->ea;
237
238 $ea->begin ($map, $x, $y, $self);
239
240 $ea->edit ($map, $x, $y, $self)
241 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
242
243 $self->{draw_mode} = [$x, $y];
244 }
245
246 sub stop_drawmode {
247 my ($self, $map) = @_;
248
249 $self->{draw_mode} or return;
250
251 my ($x, $y) = $map->coord ($map->get_pointer);
252
253 my $ea = $self->ea;
254 $ea->end ($map, $x, $y, $self);
255
256 delete $self->{draw_mode};
257 }
258
259 #################################################################
260 ###### UTILITY FUNCTIONS ########################################
261 #################################################################
262
263 sub follow {
264 my ($self, $dir) = @_;
265
266 my %dir_to_path = (
267 u => 'tile_path_1',
268 d => 'tile_path_3',
269 r => 'tile_path_2',
270 l => 'tile_path_4',
271 );
272
273 defined $dir_to_path{$dir}
274 or return;
275 my $map = $self->{map}{map}{info}{$dir_to_path{$dir}}
276 or return;
277
278 $map = map2abs ($map, $self);
279 $::MAINWIN->open_map_editor ($map);
280 }
281
282 # FIXME: Fix the automatic update of the attribute editor! and also the stack view!
283 sub undo {
284 my ($self) = @_;
285
286 my $map = $self->{map}; # the Crossfire::MapWidget
287
288 $map->{undo_stack_pos}
289 or return;
290
291 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
292 }
293
294 sub get_stack_refs {
295 my ($self, $map, $x, $y) = @_;
296
297 my $cstack = $map->get ($x, $y);
298
299 return [] unless @$cstack;
300
301 my @refs;
302
303 for my $arch (@$cstack) {
304 my ($ox, $oy) = ($x, $y);
305 if ($arch->{_virtual}) {
306 $ox = $arch->{virtual_x};
307 $oy = $arch->{virtual_y};
308 $arch = $arch->{_virtual};
309 $cstack = $map->get ($ox, $oy);
310 # XXX: This heavily blows up if $arch isn't on $cstack now.. and it actually really does :(
311 }
312
313 push @refs,
314 GCE::ArchRef->new (
315 arch => $arch,
316 cb => sub {
317 $map->change_begin ('attredit');
318 $map->change_stack ($ox, $oy, $cstack);
319
320 if (my $changeset = $map->change_end) {
321 splice @{ $map->{undo_stack} ||= [] },
322 $map->{undo_stack_pos}++, 1e6,
323 $changeset;
324 }
325 }
326 );
327 }
328
329 return @refs;
330 }
331
332 sub redo {
333 my ($self) = @_;
334
335 my $map = $self->{map}; # the Crossfire::MapWidget
336
337 $map->{undo_stack}
338 and $map->{undo_stack_pos} < @{$map->{undo_stack}}
339 or return;
340
341 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
342 }
343
344 sub load_meta_info {
345 my ($mapfile) = @_;
346 if (-e "$mapfile.meta") {
347 open my $metafh, "<", "$mapfile.meta"
348 or warn "Couldn't open meta file $mapfile.meta: $!";
349 my $metadata = do { local $/; <$metafh> };
350 return Crossfire::from_json ($metadata);
351 }
352 }
353
354 sub save_meta_info {
355 my ($mapfile, $metainfo) = @_;
356 open my $metafh, ">", "$mapfile.meta"
357 or warn "Couldn't write meta file $mapfile.meta: $!";
358 print $metafh Crossfire::to_json ($metainfo);
359 }
360
361 sub open_map {
362 my ($self, $path, $key) = @_;
363
364 $self->{mapkey} = $key;
365
366
367 if (ref $path) {
368 $self->{map}->set_map ($path);
369 delete $self->{meta_info};
370 $self->set_title ('<ram>');
371
372 } else {
373 $self->{path} = $path;
374 $self->{map}->set_map (my $m = new_from_file Crossfire::Map $path);
375 $self->{meta_info} = load_meta_info ($path);
376 $self->set_title ("gce - map editor - $self->{path}");
377 }
378 $self->close_windows;
379 }
380
381 sub save_map {
382 my ($self) = @_;
383
384 if ($self->{path}) {
385 $self->{map}{map}->write_file ($self->{path});
386 if ($self->{meta_info}) {
387 save_meta_info ($self->{path}, $self->{meta_info});
388 }
389 quick_msg ($self, "saved to $self->{path}");
390 $self->set_title ("gce - map editor - $self->{path}");
391 } else {
392 $self->save_map_as;
393 }
394 }
395
396 sub save_map_as {
397 my ($self) = @_;
398
399 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
400
401 if ('ok' eq $fc->run) {
402
403 $::MAINWIN->{fc_last_folder} = $fc->get_current_folder;
404 $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++;
405
406 $self->{map}{map}->write_file ($self->{path} = $fc->get_filename);
407 if ($self->{meta_info}) {
408 save_meta_info ($self->{path}, $self->{meta_info});
409 }
410 quick_msg ($self, "saved to $self->{path}");
411 $self->set_title ("gce - map editor - $self->{path}");
412 }
413
414 $fc->destroy;
415 }
416
417 #################################################################
418 ###### DIALOGOUES ###############################################
419 #################################################################
420
421 sub open_resize_map {
422 my ($self) = @_;
423
424 return if $self->{meta_info_win};
425
426 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
427
428 $w->init (
429 dialog_default_size => [500, 200, 220, 20],
430 layout_name => 'resize_win',
431 title => 'resize map',
432 ref_hash => $self->{map}{map}{info},
433 dialog => [
434 [width => 'Width' => 'string'],
435 [height => 'Height' => 'string'],
436 ],
437 save_cb => sub {
438 my ($info) = @_;
439 $self->{map}{map}->resize ($info->{width}, $info->{height});
440 $self->{map}->invalidate_all;
441 $w->destroy;
442 }
443 );
444
445 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
446
447 $w->show_all;
448 }
449
450 sub open_attach_edit {
451 my ($self) = @_;
452
453 my $w = GCE::AttachEditor->new;
454 $w->set_attachment (
455 $self->{map}{map}{info}{attach},
456 sub {
457 if (@{$_[0]}) {
458 $self->{map}{map}{info}{attach} = $_[0]
459 } else {
460 delete $self->{map}{map}{info}{attach};
461 }
462 }
463 );
464 $self->{attach_editor} = $w;
465 $w->signal_connect (destroy => sub { delete $self->{attach_editor} });
466 $w->show_all;
467 }
468
469 sub upload_map_incl {
470 my ($self) = @_;
471
472 my $meta = dclone $self->{meta_info};
473
474 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
475
476 $w->init (
477 dialog_default_size => [500, 300, 220, 20],
478 layout_name => 'map_upload_incl',
479 title => 'gce - map inclusion upload',
480 ref_hash => $meta,
481 text_entry => { key => 'changes', label => 'Changes (required for inclusion):' },
482 dialog => [
483 [gameserver => 'Game server' => 'label'],
484 [testserver => 'Test server' => 'label'],
485 [undef => x => 'sep' ],
486 [cf_login => 'Server login name' => 'string'],
487 [cf_password=> 'Password' => 'password'],
488 [path => 'Map path' => 'string'],
489 ],
490 save_cb => sub {
491 my ($meta) = @_;
492 warn "UPLOAD[".Crossfire::to_json ($meta)."]\n";
493 }
494 );
495
496 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
497
498 $w->show_all;
499 }
500
501 sub upload_map_test {
502 my ($self) = @_;
503
504 my $meta = dclone $self->{meta_info};
505
506 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
507
508 $w->init (
509 dialog_default_size => [500, 300, 220, 20],
510 layout_name => 'map_upload_test',
511 title => 'gce - map test upload',
512 ref_hash => $meta,
513 dialog => [
514 [gameserver => 'Game server' => 'string'],
515 [testserver => 'Test server' => 'string'],
516 [undef => x => 'sep' ],
517 [cf_login => 'Server login name' => 'string'],
518 [cf_password=> 'Password' => 'password'],
519 [path => 'Map path' => 'string'],
520 ],
521 save_cb => sub {
522 my ($meta) = @_;
523 warn "UPLOAD[".Crossfire::to_json ($meta)."]\n";
524 }
525 );
526
527 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
528
529 $w->show_all;
530
531
532 }
533
534 sub open_meta_info {
535 my ($self) = @_;
536
537 return if $self->{meta_info_win};
538
539 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
540
541 $w->init (
542 dialog_default_size => [500, 300, 220, 20],
543 layout_name => 'meta_info_win',
544 title => 'meta info',
545 ref_hash => $self->{meta_info},
546 dialog => [
547 [path => 'Map path' => 'string'],
548 [cf_login => 'Login name' => 'string'],
549 [revision => 'CVS Revision' => 'label'],
550 [cvs_root => 'CVS Root' => 'label'],
551 [lib_root => 'LIB Root' => 'label'],
552 [testserver => 'Test server' => 'label'],
553 [gameserver => 'Game server' => 'label'],
554 ],
555 );
556
557 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
558
559 $w->show_all;
560 }
561
562 sub open_map_prop {
563 my ($self) = @_;
564
565 return if $self->{map_properties};
566
567 my $w = $self->{map_properties} = GCE::HashDialogue->new ();
568
569 $w->init (
570 dialog_default_size => [500, 500, 220, 20],
571 layout_name => 'map_prop_win',
572 title => 'map properties',
573 ref_hash => $self->{map}{map}{info},
574 dialog => [
575 [qw/name Name string/],
576 [qw/region Region string/],
577 [qw/enter_x Enter-x string/],
578 [qw/enter_y Enter-y string/],
579 [qw/reset_timeout Reset-timeout string/],
580 [qw/swap_time Swap-timeout string/],
581 [undef, qw/x sep/],
582 [qw/difficulty Difficulty string/],
583 [qw/windspeed Windspeed string/],
584 [qw/pressure Pressure string/],
585 [qw/humid Humid string/],
586 [qw/temp Temp string/],
587 [qw/darkness Darkness string/],
588 [qw/sky Sky string/],
589 [qw/winddir Winddir string/],
590 [undef, qw/x sep/],
591 [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
592 [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
593 [undef, qw/x sep/],
594 # [qw/msg Text text/],
595 # [qw/maplore Maplore text/],
596 [qw/outdoor Outdoor check/],
597 [qw/unique Unique check/],
598 [qw/fixed_resettime Fixed-resettime check/],
599 [undef, qw/x sep/],
600 [qw/tile_path_1 Northpath string/],
601 [qw/tile_path_2 Eastpath string/],
602 [qw/tile_path_3 Southpath string/],
603 [qw/tile_path_4 Westpath string/],
604 [qw/tile_path_5 Toppath string/],
605 [qw/tile_path_6 Bottompath string/],
606 [undef, qw/x sep/],
607 [undef, 'For shop description look in the manual',
608 'button', sub { $::MAINWIN->show_help_window }],
609 [qw/shopmin Shopmin string/],
610 [qw/shopmax Shopmax string/],
611 [qw/shoprace Shoprace string/],
612 [qw/shopgreed Shopgreed string/],
613 [qw/shopitems Shopitems string/],
614 ]
615 );
616
617 $w->signal_connect (destroy => sub { delete $self->{map_properties} });
618 $w->show_all;
619 }
620
621 #################################################################
622 ###### MAP EDITOR INIT ##########################################
623 #################################################################
624
625 sub INIT_INSTANCE {
626 my ($self) = @_;
627
628 $self->set_title ('gce - map editor');
629 $self->add (my $vb = Gtk2::VBox->new);
630
631 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
632
633 $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0);
634
635 $map->signal_connect_after (key_press_event => sub {
636 my ($map, $event) = @_;
637
638 my $kv = $event->keyval;
639
640 my $ret = 0;
641
642 my ($x, $y) = $map->coord ($map->get_pointer);
643 for ([Control_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{erase} }],
644 [Alt_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{pick} }],
645 [c => sub { $::MAINWIN->{edit_collection}{select}->copy }],
646 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
647 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
648 )
649 {
650 my $ed = $_;
651
652 if ($kv == $Gtk2::Gdk::Keysyms{$ed->[0]}) {
653 my $was_in_draw = defined $self->{draw_mode};
654
655 $self->stop_drawmode ($map)
656 if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
657
658 $ed->[1]->();
659 $ret = 1;
660
661 $self->start_drawmode ($map)
662 if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
663 }
664 }
665
666 if ($self->ea->special_arrow) {
667 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
668 } else {
669 # FIXME: Get the original cursor and insert it here
670 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
671 }
672
673 $ret
674 });
675
676 $map->signal_connect_after (key_release_event => sub {
677 my ($map, $event) = @_;
678
679 my $ret = 0;
680
681 if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L}
682 or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L})
683 {
684 my $was_in_draw = defined $self->{draw_mode};
685
686 $self->stop_drawmode ($map)
687 if $was_in_draw;
688
689 delete $self->{ea_alt};
690 $ret = 1;
691
692 $self->start_drawmode ($map)
693 if $was_in_draw;
694 }
695
696 if ($self->ea->special_arrow) {
697 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
698 } else {
699 # FIXME: Get the original cursor and insert it here
700 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
701 }
702
703 $ret
704 });
705 $map->signal_connect_after (button_press_event => sub {
706 my ($map, $event) = @_;
707
708 if ((not $self->{draw_mode}) and $event->button == 1) {
709 my $ea = $self->ea;
710
711 $self->start_drawmode ($map);
712
713 $ea->want_cursor
714 or $map->disable_tooltip;
715
716 return 1;
717 } elsif ($event->button == 3) {
718 $self->do_context_menu ($map, $event);
719 return 1;
720 }
721
722 0
723 });
724
725 $map->signal_connect_after (motion_notify_event => sub {
726 my ($map, $event) = @_;
727
728 $self->{draw_mode}
729 or return;
730
731 my $ea = $self->ea;
732
733 my ($X, $Y) = @{$self->{draw_mode}}[0,1];
734 my ($x, $y) = $map->coord ($map->get_pointer);
735
736 while ($x != $X || $y != $Y) {
737
738 $X++ if $X < $x;
739 $X-- if $X > $x;
740 $Y++ if $Y < $y;
741 $Y-- if $Y > $y;
742
743 unless ($ea->only_on_click) {
744 $ea->edit ($map, $X, $Y, $self)
745 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
746 }
747 }
748
749 @{$self->{draw_mode}}[0,1] = ($X, $Y);
750
751 1
752 });
753
754 $map->signal_connect_after (button_release_event => sub {
755 my ($map, $event) = @_;
756
757 if ($self->{draw_mode} and $event->button == 1) {
758 my $ea = $self->ea;
759
760 $self->stop_drawmode ($map);
761
762 $ea->want_cursor
763 or $map->enable_tooltip;
764
765 return 1;
766 }
767
768 0
769 });
770
771 ::set_pos_and_size ($self, $main::CFG->{map_window}, 500, 500, 200, 0);
772 }
773
774
775
776 =head1 AUTHOR
777
778 Marc Lehmann <schmorp@schmorp.de>
779 http://home.schmorp.de/
780
781 Robin Redeker <elmex@ta-sa.org>
782 http://www.ta-sa.org/
783
784 =cut
785 1;
786