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.73.2.2 by elmex, Tue Jul 7 15:42:18 2009 UTC

8 8
9use Gtk2; 9use Gtk2;
10use Gtk2::Gdk::Keysyms; 10use Gtk2::Gdk::Keysyms;
11use Gtk2::SimpleMenu; 11use Gtk2::SimpleMenu;
12 12
13use Crossfire; 13use Deliantra;
14use Crossfire::Map; 14use Deliantra::Map;
15use Crossfire::MapWidget; 15use Deliantra::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 Scalar::Util qw/weaken/;
27use Storable qw/dclone/;
28
29use Guard;
30
23use strict; 31use strict;
32
33#################################################################
34###### WINDOW MANAGEMENT ########################################
35#################################################################
36
37sub save_layout {
38 my ($self) = @_;
39
40 $self->{attach_editor}->save_layout if $self->{attach_editor};
41 $self->{map_properties}->save_layout if $self->{map_properties};
42 $self->{meta_info_win}->save_layout if $self->{meta_info_win};
43
44 $main::CFG->{map_info} = main::get_pos_and_size ($self->{map_info})
45 if $self->{map_info};
46}
47
48sub 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
60sub do_context_menu {
61 my ($self, $map, $event) = @_;
62
63 my ($x, $y) = $map->coord ($event->x, $event->y);
64
65 my $menu = Gtk2::Menu->new;
66 foreach my $cm (
67 [
68 Follow => sub {
69 $::MAINWIN->{edit_collection}{followexit}->edit ($map, $x, $y, $self)
70 },
71 ]
72 ) {
73 my $item = Gtk2::MenuItem->new ($cm->[0]);
74 $menu->append ($item);
75 $item->show;
76 $item->signal_connect (activate => $cm->[1]);
77 }
78
79 $menu->append (my $sep = new Gtk2::SeparatorMenuItem);
80 $sep->show;
81
82 for my $sr (reverse $self->get_stack_refs ($map, $x, $y)) {
83 my $item = Gtk2::MenuItem->new ($sr->longname);
84 $menu->append ($item);
85 $item->set_submenu (my $smenu = new Gtk2::Menu);
86
87 for my $act (
88 [ 'Add inventory' => sub { $_[0]->add_inv ($::MAINWIN->get_pick) } ],
89 [ 'Find in picker' => sub { $::MAINWIN->open_pick_window ({ selection => $sr->picker_folder }) } ],
90 ) {
91 my $sitem = Gtk2::MenuItem->new ($act->[0]);
92 $smenu->append ($sitem);
93 $sitem->signal_connect (activate => sub { $act->[1]->($sr) });
94 $sitem->show;
95 }
96
97 $item->show;
98 }
99
100 $menu->popup (undef, undef, undef, undef, $event->button, $event->time);
101}
24 102
25sub build_menu { 103sub build_menu {
26 my ($self) = @_; 104 my ($self) = @_;
27 105
28 my $menu_tree = [ 106 my $menu_tree = [
34 accelerator => '<ctrl>S' 112 accelerator => '<ctrl>S'
35 }, 113 },
36 "Save As" => { 114 "Save As" => {
37 callback => sub { $self->save_map_as }, 115 callback => sub { $self->save_map_as },
38 }, 116 },
117 "Map _Info" => {
118 callback => sub { $self->open_map_info },
119 accelerator => "<ctrl>I",
120 },
121 "Map _Properties" => {
122 callback => sub { $self->open_map_prop },
123 accelerator => "<ctrl>P"
124 },
125 "Map _Attachments" => {
126 callback => sub { $self->open_attach_edit },
127 accelerator => "<ctrl>A"
128 },
129# "Map Meta _Info" => {
130# callback => sub { $self->open_meta_info },
131# },
132# Upload => {
133# item_type => '<Branch>',
134# children => [
135# "Upload for testing" => {
136# callback => sub { $self->upload_map_test },
137# },
138# "Upload for inclusion" => {
139# callback => sub { $self->upload_map_incl },
140# },
141# ]
142# },
143 "_Map Resize" => {
144 callback => sub { $self->open_resize_map },
145 },
39 "Close" => { 146 "Close" => {
40 callback => sub { $self->delete; 1 }, 147 callback => sub { $self->destroy },
148 accelerator => "<ctrl>W",
41 }, 149 },
42 ] 150 ]
43 }, 151 },
44 _Edit => { 152 _Edit => {
45 item_type => '<Branch>', 153 item_type => '<Branch>',
50 }, 158 },
51 "_Redo" => { 159 "_Redo" => {
52 callback => sub { $self->redo }, 160 callback => sub { $self->redo },
53 accelerator => "<ctrl>Y" 161 accelerator => "<ctrl>Y"
54 }, 162 },
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 ] 163 ]
64 }, 164 },
65 _Go => { 165 _Go => {
66 item_type => '<Branch>', 166 item_type => '<Branch>',
67 children => [ 167 children => [
68 "_Up" => { 168 "_Up" => {
69 callback => sub { $self->follow ('u') }, 169 callback => sub { $self->follow ('u') },
70 accelerator => "<ctrl>U" 170 accelerator => "<ctrl>Up"
71 }, 171 },
72 "_Down" => { 172 "_Down" => {
73 callback => sub { $self->follow ('d') }, 173 callback => sub { $self->follow ('d') },
74 accelerator => "<ctrl>D" 174 accelerator => "<ctrl>Down"
75 }, 175 },
76 "_Right" => { 176 "_Right" => {
77 callback => sub { $self->follow ('r') }, 177 callback => sub { $self->follow ('r') },
78 accelerator => "<ctrl>R" 178 accelerator => "<ctrl>Right"
79 }, 179 },
80 "_Left" => { 180 "_Left" => {
81 callback => sub { $self->follow ('l') }, 181 callback => sub { $self->follow ('l') },
82 accelerator => "<ctrl>L" 182 accelerator => "<ctrl>Left"
83 }, 183 },
84 ] 184 ]
85 }, 185 },
86 _Help => { 186 _Help => {
87 item_type => '<Branch>', 187 item_type => '<Branch>',
88 children => [ 188 children => [
89 _Help => { 189 _Manual => {
90 callback => sub { $::MAINWIN->show_help_window }, 190 callback => sub { $::MAINWIN->show_help_window },
91 accelerator => "<ctrl>H" 191 accelerator => "<ctrl>H"
92 }, 192 },
93 ] 193 ]
94 }, 194 },
104 [i => 'pick'], 204 [i => 'pick'],
105 [p => 'place'], 205 [p => 'place'],
106 [e => 'erase'], 206 [e => 'erase'],
107 [s => 'select'], 207 [s => 'select'],
108 [l => 'eval'], 208 [l => 'eval'],
109 [x => 'connectexit'], 209 [t => 'connect'],
110 [f => 'followexit'] 210 [f => 'followexit']
111 ) 211 )
112 { 212 {
113 my $tool = $_->[1]; 213 my $tool = $_->[1];
114 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible', 214 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
115 sub { $::MAINWIN->set_edit_tool ($tool) }); 215 sub { $::MAINWIN->set_edit_tool ($tool) });
116 } 216 }
117 217
218 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{'r'}, ['control-mask'], 'visible',
219 sub { $self->redo });
220
118 $self->add_accel_group ($men->{accel_group}); 221 $self->add_accel_group ($men->{accel_group});
119 222
120 return $men->{widget}; 223 return $men->{widget};
121} 224}
122 225
226#################################################################
227###### EDIT TOOL STUFF ##########################################
228#################################################################
229
230sub set_edit_tool {
231 my ($self, $tool) = @_;
232
233 $self->{etool} = $tool;
234
235 if ($self->ea->special_arrow) {
236 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
237 } else {
238 # FIXME: Get the original cursor and insert it here
239 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
240 }
241}
242
123sub ea { 243sub ea {
124 my ($self) = @_; 244 my ($self) = @_;
125 $self->{ea_alt} || $::MAINWIN->{sel_editaction}; 245 $self->{ea_alt} || $self->{etool};
126} 246}
127 247
128sub INIT_INSTANCE { 248sub start_drawmode {
129 my ($self) = @_; 249 my ($self, $map) = @_;
130 250
131 $self->set_title ('gce - map editor'); 251 $self->{draw_mode} and return;
132 $self->add (my $vb = Gtk2::VBox->new);
133 252
134 $self->signal_connect (delete_event => sub { $self->delete; 1 }); 253 # XXX: is this okay? my ($x, $y) = $map->coord ($event->x, $event->y);
135
136 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
137
138 $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0);
139
140 $self->signal_connect (focus_in_event => sub {
141 my $ea = $::MAINWIN->{sel_editaction};
142 if ($ea->special_arrow) {
143 $self->{map}->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($ea->special_arrow));
144 } else {
145 $self->{map}->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
146 }
147 delete $self->{ea_alt};
148 });
149
150 $map->signal_connect (key_press_event => sub {
151 my ($map, $event) = @_;
152
153 my $kv = $event->keyval;
154
155 my ($x, $y) = $map->coord ($map->get_pointer); 254 my ($x, $y) = $map->coord ($map->get_pointer);
156 for ([Control_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{erase} }],
157 [Alt_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{pick} }],
158 [c => sub { $::MAINWIN->{edit_collection}{select}->copy }],
159 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
160 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
161 )
162 {
163 if ($kv == $Gtk2::Gdk::Keysyms{$_->[0]}) {
164 $_->[1]->();
165 }
166 }
167 255
168 if ($self->ea->special_arrow) {
169 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
170 }
171 1;
172 });
173 $map->signal_connect (key_release_event => sub {
174 my ($map, $event) = @_;
175
176 if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L}
177 or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L})
178 {
179 delete $self->{ea_alt};
180 }
181
182 if ($self->ea->special_arrow) {
183 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
184 } else {
185 # XXX: Get the original cursor and insert it here
186 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
187 }
188 1;
189 });
190 $map->signal_connect (button_press_event => sub {
191 my ($map, $event) = @_;
192
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) {
197
198 my $ea = $self->ea; 256 my $ea = $self->ea;
199 257
200 $ea->begin ($map, $x, $y, $self) 258 $ea->begin ($map, $x, $y, $self);
259
260 $ea->edit ($map, $x, $y, $self)
201 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height}; 261 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
202 262
203 $self->{draw_mode} = [$x, $y]; 263 $self->{draw_mode} = [$x, $y];
264}
204 265
205 $ea->want_cursor 266sub stop_drawmode {
206 or $map->disable_tooltip; 267 my ($self, $map) = @_;
207 268
208 return 1;
209 }
210 0
211 });
212
213 $map->signal_connect_after (motion_notify_event => sub {
214 my ($map, $event) = @_;
215
216 $self->{draw_mode} 269 $self->{draw_mode} or return;
217 or return;
218 270
219 my $ea = $self->ea;
220
221 my ($X, $Y) = @{$self->{draw_mode}}[0,1];
222 my ($x, $y) = $map->coord ($map->get_pointer); 271 my ($x, $y) = $map->coord ($map->get_pointer);
223 272
224 while ($x != $X || $y != $Y) {
225
226 $X++ if $X < $x;
227 $X-- if $X > $x;
228 $Y++ if $Y < $y;
229 $Y-- if $Y > $y;
230
231 $ea->edit ($map, $X, $Y, $self)
232 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
233 }
234
235 @{$self->{draw_mode}}[0,1] = ($X, $Y);
236
237 1
238 });
239
240 $map->signal_connect (button_release_event => sub {
241 my ($map, $event) = @_;
242
243 if ($self->{draw_mode}) {
244 my ($x, $y) = $map->coord ($map->get_pointer);
245
246 my $ea = $self->ea; 273 my $ea = $self->ea;
247 $ea->end ($map, $x, $y, $self); 274 $ea->end ($map, $x, $y, $self);
248 275
249 delete $self->{draw_mode}; 276 delete $self->{draw_mode};
250
251 $ea->want_cursor
252 or $map->enable_tooltip;
253
254 return 1;
255 }
256
257 0
258 });
259} 277}
260 278
261# FIXME: Fix the automatic update of the attribute editor! and also the stack view! 279#################################################################
262sub undo { 280###### UTILITY FUNCTIONS ########################################
263 my ($self) = @_; 281#################################################################
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 282
415sub follow { 283sub follow {
416 my ($self, $dir) = @_; 284 my ($self, $dir) = @_;
417 285
418 my %dir_to_path = ( 286 my %dir_to_path = (
429 297
430 $map = map2abs ($map, $self); 298 $map = map2abs ($map, $self);
431 $::MAINWIN->open_map_editor ($map); 299 $::MAINWIN->open_map_editor ($map);
432} 300}
433 301
302# FIXME: Fix the automatic update of the attribute editor! and also the stack view!
303sub undo {
304 my ($self) = @_;
305
306 my $map = $self->{map}; # the Deliantra::MapWidget
307
308 $map->{undo_stack_pos}
309 or return;
310
311 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
312
313 $::MAINWIN->update_stack_view ();
314}
315
316sub 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 my ($ix, $iy, $iarch, $istack) = devirtualize ($map, $x, $y, $arch, $cstack);
327 push @refs,
328 GCE::ArchRef->new (
329 arch => $iarch,
330 source => 'map',
331 cb => sub {
332 $map->change_begin ('attredit');
333 $map->change_stack ($ix, $iy, $istack);
334
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
347sub redo {
348 my ($self) = @_;
349
350 my $map = $self->{map}; # the Deliantra::MapWidget
351
352 $map->{undo_stack}
353 and $map->{undo_stack_pos} < @{$map->{undo_stack}}
354 or return;
355
356 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
357}
358
359sub 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 return Deliantra::decode_json ($metadata);
366 }
367}
368
369sub save_meta_info {
370 my ($mapfile, $metainfo) = @_;
371 open my $metafh, ">", "$mapfile.meta"
372 or warn "Couldn't write meta file $mapfile.meta: $!";
373 print $metafh Deliantra::encode_json ($metainfo);
374}
375
376sub open_map {
377 my ($self, $path, $key) = @_;
378
379 $self->{mapkey} = $key;
380
381 if (ref $path) {
382 $self->{map}->set_map ($path);
383 delete $self->{meta_info};
384 $self->set_window_title;
385
386 } else {
387 my $ok = 0;
388 if (-e $path && -f $path) {
389 $ok = 1;
390 } else {
391 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 die "Couldn't open '$path' or find '$path.map': No such file or it is not a file.\n";
400 }
401 $self->{path} = $path;
402 $self->{map}->set_map (my $m = new_from_file Deliantra::Map $path);
403 $self->{meta_info} = load_meta_info ($path);
404 $self->set_window_title ($self->{path});
405 $::MAINWIN->add_recent($path);
406 }
407
408 $self->update_overlays;
409 $self->close_windows;
410}
411
412sub save_map {
413 my ($self) = @_;
414
415 if ($self->{path}) {
416 $self->{map}{map}->write_file ($self->{path});
417 if ($self->{meta_info}) {
418 save_meta_info ($self->{path}, $self->{meta_info});
419 }
420 quick_msg ($self, "saved to $self->{path}");
421 $self->set_window_title ($self->{path});
422 $::MAINWIN->add_recent($self->{path});
423 } else {
424 $self->save_map_as;
425 }
426}
427
428sub set_window_title {
429 my ($self, $title) = @_;
430
431 $title = 'Unsaved'
432 unless $title;
433
434 $self->set_title(File::Basename::basename($title).' - deliantra editor');
435}
436
437sub save_map_as {
438 my ($self) = @_;
439
440 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
441
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 if($fc->get_filename) {
448 $self->{path} = $fc->get_filename;
449 $self->save_map;
450 }
451 }
452
453 $fc->destroy;
454}
455
456sub _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
471sub update_overlays {
472 my ($self, $sx, $sy, $stack) = @_;
473 my $conns = {};
474
475 if (not $stack) {
476 my ($w, $h) = ($self->{map}{map}{width}, $self->{map}{map}{height});
477
478 for (my $x = 0; $x < $w; $x++) {
479 for (my $y = 0; $y < $h; $y++) {
480 _get_conns_for_obj ($_, $x, $y, $conns)
481 for @{$self->{map}->get_ro ($x, $y)};
482 }
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 _get_conns_for_obj ($_, $sx, $sy, $conns)
494 for @$stack;
495 }
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 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
512 $self->{map}->overlay (
513 $ovlname, $x * TILESIZE, $y * TILESIZE, TILESIZE, TILESIZE,
514 sub {
515 my ($mapwin, $x, $y) = @_;
516 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 $mapwin->{window}->draw_layout_with_colors (
535 $mapwin->style->black_gc, $x + 1, $y + 1, $pl,
536 $self->{connection_overlay_foreground},
537 $self->{connection_overlay_background},
538 )
539 }
540 );
541 }
542 }
543}
544
545#################################################################
546###### DIALOGOUES ###############################################
547#################################################################
548
549sub open_resize_map {
550 my ($self) = @_;
551
552 return if $self->{meta_info_win};
553
554 my $w = $self->{meta_info_win} = GCE::HashDialogue->new ();
555
556 $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 close_on_save => 1,
566 save_button_label => 'resize',
567 save_cb => sub {
568 my ($info) = @_;
569 $self->{map}{map}->resize ($info->{width}, $info->{height});
570 $self->{map}->set_map ($self->{map}{map});
571 $self->update_overlays;
572 }
573 );
574
575 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
576
577 $w->show_all;
578}
579
580sub 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
599sub 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 close_on_save => 1,
621 save_cb => sub {
622 my ($meta) = @_;
623 warn "UPLOAD[".Deliantra::encode_json ($meta)."]\n";
624 }
625 );
626
627 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
628
629 $w->show_all;
630}
631
632sub 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 warn "UPLOAD[".Deliantra::encode_json ($meta)."]\n";
655 }
656 );
657
658 $w->signal_connect (destroy => sub { delete $self->{meta_info_win} });
659
660 $w->show_all;
661
662
663}
664
665sub open_meta_info {
666 my ($self) = @_;
667
668 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}
692
693sub open_map_info {
694 my ($self) = @_;
695 return if $self->{map_info};
696
697 my $w = $self->{map_info} = Gtk2::Window->new ('toplevel');
698 $w->set_title ("deliantra editor - map info");
699
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
434sub open_map_prop { 735sub open_map_prop {
435 my ($self) = @_; 736 my ($self) = @_;
436 737
738 return if $self->{map_properties};
437 739
438 my $w = Gtk2::Window->new ('toplevel'); 740 my $w = $self->{map_properties} = GCE::HashDialogue->new ();
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 741
445 my $i = 0; 742 $w->init (
446 for ( 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 close_on_save => 1,
748 dialog => [
447 [qw/name Name string/], 749 [qw/name Name string/],
448 [qw/region Region string/], 750 [qw/region Region string/],
449 [qw/enter_x Enter-x string/], 751 [qw/enter_x Enter-x string/],
450 [qw/enter_y Enter-y string/], 752 [qw/enter_y Enter-y string/],
451 [qw/reset_timeout Reset-timeout string/], 753 [qw/reset_timeout Reset-timeout string/],
452 [qw/swap_time Swap-timeout string/], 754 [qw/swap_time Swap-timeout string/],
453 [qw/x x sep/], 755 [undef, qw/x sep/],
454 [qw/difficulty Difficulty string/], 756 [qw/difficulty Difficulty string/],
455 [qw/windspeed Windspeed string/], 757 [qw/windspeed Windspeed string/],
456 [qw/pressure Pressure string/], 758 [qw/pressure Pressure string/],
457 [qw/humid Humid string/], 759 [qw/humid Humid string/],
458 [qw/temp Temp string/], 760 [qw/temp Temp string/],
459 [qw/darkness Darkness string/], 761 [qw/darkness Darkness string/],
460 [qw/sky Sky string/], 762 [qw/sky Sky string/],
461 [qw/winddir Winddir string/], 763 [qw/winddir Winddir string/],
462 [qw/x x sep/], 764 [undef, qw/x sep/],
463 [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }], 765 [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]) }], 766 [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
465 [qw/x x sep/], 767 [undef, qw/x sep/],
466 [qw/msg Text text/], 768 # [qw/msg Text text/],
467 [qw/maplore Maplore text/], 769# [qw/maplore Maplore text/],
468 [qw/outdoor Outdoor check/], 770 [qw/outdoor Outdoor check/],
469 [qw/unique Unique check/], 771 [qw/unique Unique check/],
470 [qw/fixed_resettime Fixed-resettime check/], 772 [qw/fixed_resettime Fixed-resettime check/],
471 [qw/x x sep/], 773 [per_player => 'Per player' => 'check'],
774 [per_party => 'Per party' => 'check'],
775 [no_reset => 'No reset' => 'check'],
776 [music => 'Map Music' => 'string'],
777 [undef, qw/x sep/],
472 [qw/tile_path_1 Northpath string/], 778 [qw/tile_path_1 Northpath string/],
473 [qw/tile_path_2 Eastpath string/], 779 [qw/tile_path_2 Eastpath string/],
474 [qw/tile_path_3 Southpath string/], 780 [qw/tile_path_3 Southpath string/],
475 [qw/tile_path_4 Westpath string/], 781 [qw/tile_path_4 Westpath string/],
476 [qw/tile_path_5 Toppath string/], 782 [qw/tile_path_5 Toppath string/],
477 [qw/tile_path_6 Bottompath string/], 783 [qw/tile_path_6 Bottompath string/],
784 [undef, qw/x sep/],
785 [undef, 'For shop description look in the manual',
786 'button', sub { $::MAINWIN->show_help_window }],
787 [qw/shopmin Shopmin string/],
788 [qw/shopmax Shopmax string/],
789 [qw/shoprace Shoprace string/],
790 [qw/shopgreed Shopgreed string/],
791 [qw/shopitems Shopitems string/],
478 ) 792 ]
479 { 793 );
480 $self->_add_prop_entry ($t, $i++, @$_);
481 }
482 794
795 $w->signal_connect (destroy => sub { delete $self->{map_properties} });
483 $w->show_all; 796 $w->show_all;
797}
798
799#################################################################
800###### CURSORS ##################################################
801#################################################################
802
803sub cursor {
804 my ($self, $owner, $x, $y, $z, @inv_path) = @_;
805
806 my $cursmap = ($self->{_cursors} ||= []);
807 my $cursor;
808
809 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 };
822 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
833 weaken $cursmap->[$x]->[$y]->[-1];
834
835 $cursor->{z} = $z if defined $z;
836 $cursor->{inv} = \@inv_path if @inv_path;
837
838 my ($cx, $cy, $cid) = ($cursor->{x}, $cursor->{y}, 1 * $cursor);
839 $cursor->{guard} = guard {
840 if ($cid == $self->{_last_attr_edit_overlay_cid}) {
841 $self->{map}->overlay ('attr_edit');
842 }
843 };
844
845 $self->update_cursor_overlay ($cursor);
846 $cursor
847}
848
849sub cursors_at {
850 my ($self, $x, $y) = @_;
851 my $cursmap = ($self->{_cursors} ||= []);
852 $cursmap->[$x]->[$y] || []
853}
854
855sub update_cursor_overlay {
856 my ($self, $cursor) = @_;
857
858 if ($cursor->{owner} eq 'attr_edit') {
859 $self->{_last_attr_edit_overlay_cid} = 1 * $cursor;
860
861 $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###### MAP EDITOR INIT ##########################################
893#################################################################
894
895sub INIT_INSTANCE {
896 my ($self) = @_;
897
898 $self->set_window_title;
899 $self->add (my $vb = Gtk2::VBox->new);
900
901 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
902
903 $vb->pack_start (my $map = $self->{map} = Deliantra::MapWidget->new, 1, 1, 0);
904
905 $self->{map}->{editor} = $self;
906 weaken $self->{map}->{editor};
907
908 $map->signal_connect_after (stack_change => sub {
909 my ($map, $x, $y, $stack) = @_;
910 $self->update_overlays ($x, $y, $stack);
911 # old: $::MAINWIN->update_map_pos ($self, $x, $y);
912 });
913 $map->signal_connect_after (swap_stack_change => sub {
914 my ($map, $x, $y, $stack) = @_;
915 $self->update_overlays ($x, $y, $stack);
916 # old: $::MAINWIN->update_map_pos ($self, $x, $y);
917 });
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 # 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
928 $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 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 )
940 {
941 my $ed = $_;
942
943 if ($kv == $Gtk2::Gdk::Keysyms{$ed->[0]}) {
944 my $was_in_draw = defined $self->{draw_mode};
945
946 $self->stop_drawmode ($map) if $was_in_draw;
947
948 $ed->[1]->();
949 $ret = 1;
950
951 $self->start_drawmode ($map) if $was_in_draw;
952 }
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);
484} 1032}
485 1033
486=head1 AUTHOR 1034=head1 AUTHOR
487 1035
488 Marc Lehmann <schmorp@schmorp.de> 1036 Marc Lehmann <schmorp@schmorp.de>
490 1038
491 Robin Redeker <elmex@ta-sa.org> 1039 Robin Redeker <elmex@ta-sa.org>
492 http://www.ta-sa.org/ 1040 http://www.ta-sa.org/
493 1041
494=cut 1042=cut
4951;
496 1043
10441
1045

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines