ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.73.2.2
Committed: Tue Jul 7 15:42:18 2009 UTC (14 years, 11 months ago) by elmex
Branch: cursor
Changes since 1.73.2.1: +40 -23 lines
Log Message:
*** empty log message ***

File Contents

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