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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines