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.4 by elmex, Sat Feb 11 01:23:07 2006 UTC vs.
Revision 1.37 by elmex, Thu Jun 1 14:33:14 2006 UTC

13use Crossfire; 13use Crossfire;
14use Crossfire::Map; 14use Crossfire::Map;
15use Crossfire::MapWidget; 15use Crossfire::MapWidget;
16 16
17use GCE::AttrEdit; 17use GCE::AttrEdit;
18use GCE::Util;
18 19
19use Glib::Object::Subclass 20use Glib::Object::Subclass
20 Gtk2::VBox, 21 Gtk2::Window;
21 ;
22 22
23use strict; 23use strict;
24 24
25sub do_context_menu {
26 my ($self, $map, $event) = @_;
27
28 my ($x, $y) = $map->coord ($event->x, $event->y);
29
30 my $menu = Gtk2::Menu->new;
31 foreach my $cm (
32 [
33 Follow => sub {
34 $::MAINWIN->{edit_collection}{followexit}->edit ($map, $x, $y, $self)
35 }
36 ]
37 ) {
38 my $item = Gtk2::MenuItem->new ($cm->[0]);
39 $menu->append ($item);
40 $item->show;
41 $item->signal_connect (activate => $cm->[1]);
42 }
43 $menu->popup (undef, undef, undef, undef, $event->button, $event->time);
44}
45
46sub build_menu {
47 my ($self) = @_;
48
49 my $menu_tree = [
50 _File => {
51 item_type => '<Branch>',
52 children => [
53 "_Save" => {
54 callback => sub { $self->save_map },
55 accelerator => '<ctrl>S'
56 },
57 "Save As" => {
58 callback => sub { $self->save_map_as },
59 },
60 "_Map Properties" => {
61 callback => sub { $self->open_map_prop },
62 accelerator => "<ctrl>P"
63 },
64 "_Map Resize" => {
65 callback => sub { $self->open_resize_map },
66 accelerator => "<ctrl>R"
67 },
68 "Close" => {
69 callback => sub { $self->destroy },
70 },
71 ]
72 },
73 _Edit => {
74 item_type => '<Branch>',
75 children => [
76 "_Undo" => {
77 callback => sub { $self->undo },
78 accelerator => "<ctrl>Z"
79 },
80 "_Redo" => {
81 callback => sub { $self->redo },
82 accelerator => "<ctrl>Y"
83 },
84 ]
85 },
86 _Go => {
87 item_type => '<Branch>',
88 children => [
89 "_Up" => {
90 callback => sub { $self->follow ('u') },
91 accelerator => "<ctrl>Up"
92 },
93 "_Down" => {
94 callback => sub { $self->follow ('d') },
95 accelerator => "<ctrl>Down"
96 },
97 "_Right" => {
98 callback => sub { $self->follow ('r') },
99 accelerator => "<ctrl>Right"
100 },
101 "_Left" => {
102 callback => sub { $self->follow ('l') },
103 accelerator => "<ctrl>Left"
104 },
105 ]
106 },
107 _Help => {
108 item_type => '<Branch>',
109 children => [
110 _Manual => {
111 callback => sub { $::MAINWIN->show_help_window },
112 accelerator => "<ctrl>H"
113 },
114 ]
115 },
116 ];
117
118 my $men =
119 Gtk2::SimpleMenu->new (
120 menu_tree => $menu_tree,
121 default_callback => \&default_cb,
122 );
123
124 for (
125 [i => 'pick'],
126 [p => 'place'],
127 [e => 'erase'],
128 [s => 'select'],
129 [l => 'eval'],
130 [t => 'connect'],
131 [f => 'followexit']
132 )
133 {
134 my $tool = $_->[1];
135 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
136 sub { $::MAINWIN->set_edit_tool ($tool) });
137 }
138
139 $self->add_accel_group ($men->{accel_group});
140
141 return $men->{widget};
142}
143
144sub set_edit_tool {
145 my ($self, $tool) = @_;
146
147 $self->{etool} = $tool;
148
149 if ($self->ea->special_arrow) {
150 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
151 } else {
152 # FIXME: Get the original cursor and insert it here
153 $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
154 }
155}
156
157sub ea {
158 my ($self) = @_;
159 $self->{ea_alt} || $self->{etool};
160}
161
25sub INIT_INSTANCE { 162sub INIT_INSTANCE {
26 my ($self) = @_; 163 my ($self) = @_;
27 164
28 my $map = new Crossfire::MapWidget; 165 $self->set_title ('gce - map editor');
166 $self->add (my $vb = Gtk2::VBox->new);
29 167
30 # XXX: Make a nicer pick-view (with picture) 168 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
31 $self->pack_start (my $lbl = Gtk2::Label->new ("no selection"), 0, 1, 0);
32 $self->{pick_view} = $lbl;
33 169
34 $self->pack_start ($map, 1, 1, 0); 170 $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0);
35 $self->{map} = $map;
36 171
37 $map->signal_connect (key_press_event => sub { 172 $map->signal_connect_after (key_press_event => sub {
38 my ($map, $event) = @_; 173 my ($map, $event) = @_;
39 174
40 $event->keyval == $Gtk2::Gdk::Keysyms{Shift_L} 175 my $kv = $event->keyval;
41 and $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_QUESTION_ARROW'));
42 176
43 $event->keyval == $Gtk2::Gdk::Keysyms{Control_L} 177 my $ret = 0;
178
179 my ($x, $y) = $map->coord ($map->get_pointer);
180 for ([Control_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{erase} }],
181 [Alt_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{pick} }],
182 [c => sub { $::MAINWIN->{edit_collection}{select}->copy }],
183 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
184 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
185 )
186 {
187 my $ed = $_;
188
189 if ($kv == $Gtk2::Gdk::Keysyms{$ed->[0]}) {
190 my $was_in_draw = defined $self->{draw_mode};
191
192 $self->stop_drawmode ($map)
193 if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
194
195 $ed->[1]->();
196 $ret = 1;
197
198 $self->start_drawmode ($map)
199 if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
200 }
201 }
202
203 if ($self->ea->special_arrow) {
204 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
205 } else {
206 # FIXME: Get the original cursor and insert it here
44 and $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_HAND2')); 207 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
208 }
45 209
210 $ret
46 }); 211 });
47 212
48 # XXX: What happens if the release got lost?
49 $map->signal_connect (key_release_event => sub { 213 $map->signal_connect_after (key_release_event => sub {
50 my ($map, $event) = @_; 214 my ($map, $event) = @_;
51 215
216 my $ret = 0;
217
218 if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L}
219 or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L})
220 {
221 my $was_in_draw = defined $self->{draw_mode};
222
223 $self->stop_drawmode ($map)
224 if $was_in_draw;
225
226 delete $self->{ea_alt};
227 $ret = 1;
228
229 $self->start_drawmode ($map)
230 if $was_in_draw;
231 }
232
233 if ($self->ea->special_arrow) {
234 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
235 } else {
236 # FIXME: Get the original cursor and insert it here
52 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR')); 237 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
238 }
53 239
240 $ret
54 }); 241 });
55 $map->signal_connect (button_press_event => sub { 242 $map->signal_connect_after (button_press_event => sub {
56 my ($map, $event) = @_; 243 my ($map, $event) = @_;
57 244
58 my ($x, $y) = $map->coord ($event->x, $event->y); 245 if ((not $self->{draw_mode}) and $event->button == 1) {
246 my $ea = $self->ea;
59 247
60 if ($event->button == 1) { 248 $self->start_drawmode ($map);
61 249
62 my $s = map_get_tile_stack ($self->{map}, $x, $y); 250 $ea->want_cursor
251 or $map->disable_tooltip;
63 252
64 if ($event->state * "control-mask") {
65
66 $self->update_pick ($s->[-1]) if @$s;
67 }
68
69 unless ($event->state * "shift-mask") {
70
71 $map->disable_tooltip unless $self->{draw_mode};
72 $self->{draw_mode} = [1, $x, $y];
73 $self->place_pick ($x, $y);
74 $s = map_get_tile_stack ($self->{map}, $x, $y);
75 }
76
77 $GCE::MainWindow::MAINWIN->update_attr_editor ($s->[-1]) if @$s;
78 $GCE::MainWindow::MAINWIN->update_stack_view ($self, $s, $x, $y);
79
80 return 1; 253 return 1;
81
82 } elsif ($event->button == 3) { 254 } elsif ($event->button == 3) {
83 255 $self->do_context_menu ($map, $event);
84 $map->disable_tooltip unless $self->{draw_mode};
85 $self->{draw_mode} = [2, $x, $y];
86 $self->delete_arch ($x, $y);
87
88 my $s = map_get_tile_stack ($self->{map}, $x, $y);
89 $GCE::MainWindow::MAINWIN->update_attr_editor ($s->[-1]) if @$s;
90 $GCE::MainWindow::MAINWIN->update_stack_view ($self, $s, $x, $y);
91
92 return 1; 256 return 1;
93 } 257 }
94 258
95 0 259 0
96 }); 260 });
97 261
98 $map->signal_connect_after (motion_notify_event => sub { 262 $map->signal_connect_after (motion_notify_event => sub {
99 my ($map, $event) = @_; 263 my ($map, $event) = @_;
100 264
101 $self->{draw_mode} 265 $self->{draw_mode}
102 or return; 266 or return;
103 267
268 my $ea = $self->ea;
269
104 my ($X, $Y) = @{$self->{draw_mode}}[1,2]; 270 my ($X, $Y) = @{$self->{draw_mode}}[0,1];
105 my ($x, $y) = $map->coord ($map->get_pointer); 271 my ($x, $y) = $map->coord ($map->get_pointer);
106 272
107 while ($x != $X || $y != $Y) { 273 while ($x != $X || $y != $Y) {
108 274
109 $X++ if $X < $x; 275 $X++ if $X < $x;
110 $X-- if $X > $x; 276 $X-- if $X > $x;
111 $Y++ if $Y < $y; 277 $Y++ if $Y < $y;
112 $Y-- if $Y > $y; 278 $Y-- if $Y > $y;
113 279
114 if ($self->{draw_mode}[0] == 1) { 280 $ea->edit ($map, $X, $Y, $self)
115 281 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
116 $self->place_pick ($X, $Y);
117
118 } elsif ($self->{draw_mode}[0] == 2) {
119
120 $self->delete_arch ($X, $Y);
121 } 282 }
122 }
123 283
124 @{$self->{draw_mode}}[1,2] = ($X, $Y); 284 @{$self->{draw_mode}}[0,1] = ($X, $Y);
125 285
126 1 286 1
127 }); 287 });
128 288
129 $map->signal_connect (button_release_event => sub { 289 $map->signal_connect_after (button_release_event => sub {
130 my ($map, $event) = @_; 290 my ($map, $event) = @_;
131 291
132 $map->enable_tooltip if delete $self->{draw_mode}; 292 if ($self->{draw_mode} and $event->button == 1) {
293 my $ea = $self->ea;
294
295 $self->stop_drawmode ($map);
296
297 $ea->want_cursor
298 or $map->enable_tooltip;
299
300 return 1;
301 }
133 302
134 0 303 0
135 }); 304 });
136} 305}
137 306
138sub set_place_arch { 307sub start_drawmode {
139 my ($self, $arch) = @_; 308 my ($self, $map) = @_;
140 309
141 $self->{pick_arch} = $arch; 310 $self->{draw_mode} and return;
142}
143 311
144sub delete_arch { 312 # XXX: is this okay? my ($x, $y) = $map->coord ($event->x, $event->y);
313 my ($x, $y) = $map->coord ($map->get_pointer);
314
315 my $ea = $self->ea;
316
317 $ea->begin ($map, $x, $y, $self);
318
319 $ea->edit ($map, $x, $y, $self)
320 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
321
322 $self->{draw_mode} = [$x, $y];
323}
324
325sub stop_drawmode {
145 my ($self, $x, $y) = @_; 326 my ($self, $map) = @_;
146 327
147 defined $self->{map} 328 $self->{draw_mode} or return;
329
330 my ($x, $y) = $map->coord ($map->get_pointer);
331
332 my $ea = $self->ea;
333 $ea->end ($map, $x, $y, $self);
334
335 delete $self->{draw_mode};
336}
337
338# FIXME: Fix the automatic update of the attribute editor! and also the stack view!
339sub undo {
340 my ($self) = @_;
341
342 my $map = $self->{map}; # the Crossfire::MapWidget
343
344 $map->{undo_stack_pos}
148 or return 0; 345 or return;
149 346
150 my $s = map_pop_tile_stack ($self->{map}, $x, $y); 347 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
151 $self->{map}->update_map ($x, $y, 1, 1);
152} 348}
153 349
154sub place_pick { 350sub redo {
155 my ($self, $x, $y) = @_; 351 my ($self) = @_;
156 352
157 defined $self->{map} 353 my $map = $self->{map}; # the Crossfire::MapWidget
158 and defined $self->{pick_arch} 354
355 $map->{undo_stack}
356 and $map->{undo_stack_pos} < @{$map->{undo_stack}}
159 or return 0; 357 or return;
160 358
161 my $s = map_get_tile_stack ($self->{map}, $x, $y); 359 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
162
163 # XXX: Do a deep clone?
164 my $arch = { _name => $self->{pick_arch}->{_name} };
165
166 if (not defined ($s->[-1]) or $s->[-1]->{_name} ne $arch->{_name}) {
167
168 map_push_tile_stack ($self->{map}, $x, $y, $arch);
169 }
170
171 $self->{map}->update_map ($x, $y, 1, 1);
172} 360}
173 361
174sub open_map { 362sub open_map {
175 my ($self, $path) = @_; 363 my ($self, $path, $key) = @_;
176 364
365 $self->{mapkey} = $key;
366
367 if (ref $path) {
368 $self->{map}->set_map ($path);
369
370 } else {
371 $self->{path} = $path;
372# print "OPENMAP $path\n";
177 $self->{map}->set_map (new_from_file Crossfire::Map $path); 373 $self->{map}->set_map (my $m = new_from_file Crossfire::Map $path);
374 require Data::Dumper;
375# print "FOO:" .Data::Dumper::Dumper ($m) . "\n";
376 }
178} 377}
179 378
180sub update_pick { 379sub save_map {
380 my ($self) = @_;
381
382 if ($self->{path}) {
383 $self->{map}{map}->write_file ($self->{path});
384 quick_msg ($self, "saved to $self->{path}");
385 } else {
386 $self->save_map_as;
387 }
388}
389
390sub save_map_as {
391 my ($self) = @_;
392
393 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
394
395 if ('ok' eq $fc->run) {
396
397 $::MAINWIN->{fc_last_folder} = $fc->get_current_folder;
398 $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++;
399
400 $self->{map}{map}->write_file ($self->{path} = $fc->get_filename);
401 quick_msg ($self, "saved to $self->{path}");
402 }
403
404 $fc->destroy;
405}
406
407sub _add_prop_entry {
408 my ($self, $table, $idx, $key, $desc, $type, $changecb) = @_;
409
410 my $edwid;
411
412 if ($type eq 'string') {
413 $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
414 $edwid = Gtk2::Entry->new;
415 $edwid->set_text ($self->{map}{map}{info}{$key});
416 $edwid->signal_connect (changed => sub {
417 $self->{map}{map}{info}{$key} = $_[0]->get_text;
418 if ($changecb) {
419 $changecb->($_[0]->get_text);
420 }
421 });
422 $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
423
424 } elsif ($type eq 'button') {
425 $table->attach_defaults (my $b = Gtk2::Button->new_with_label ($desc), 0, 2, $idx, $idx + 1);
426 $b->signal_connect (clicked => ($changecb || sub {}));
427
428 } elsif ($type eq 'label') {
429 $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
430 $edwid = Gtk2::Label->new ($self->{map}{map}{info}{$key});
431 $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
432
433 } elsif ($type eq 'check') {
434 $table->attach_defaults (my $lbl1 = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
435 $table->attach_defaults (my $lbl = Gtk2::CheckButton->new, 1, 2, $idx, $idx + 1);
436 $lbl->set_active ($self->{map}{map}{info}{$key});
437 $lbl->signal_connect (toggled => sub {
438 my ($lbl) = @_;
439 $self->{map}{map}{info}{$key} = $lbl->get_active * 1;
440 ($changecb || sub {})->($lbl->get_active);
441 });
442
443 } elsif ($type eq 'sep') {
444 $table->attach_defaults (my $lbl1 = Gtk2::HSeparator->new, 0, 2, $idx, $idx + 1);
445 } else {
446 $edwid = Gtk2::Label->new ("FOO");
447 }
448}
449
450sub open_resize_map {
451 my ($self) = @_;
452
453 my $w = Gtk2::Window->new ('toplevel');
454 $w->set_default_size (250, 150);
455 $w->add (my $sw = Gtk2::ScrolledWindow->new);
456 $sw->add_with_viewport (my $v = Gtk2::VBox->new);
457 $sw->set_policy ('automatic', 'automatic');
458 $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
459
460 my $i = 0;
461 for (
462 [qw/width Width string/],
463 [qw/height Height string/],
464 [qw/save Save button/,
465 sub {
466 $self->{map}{map}->resize ($self->{map}{map}{info}{width}, $self->{map}{map}{info}{height});
467 $self->{map}->invalidate_all;
468 $w->destroy;
469 }
470 ],
471 )
472 {
473 $self->_add_prop_entry ($t, $i++, @$_);
474 }
475
476 $w->show_all;
477}
478
479sub follow {
181 my ($self, $arch) = @_; 480 my ($self, $dir) = @_;
182 481
183 $self->{pick_arch} = $arch; 482 my %dir_to_path = (
184 $self->{pick_view}->set_text ($arch->{_name}); 483 u => 'tile_path_1',
484 d => 'tile_path_3',
485 r => 'tile_path_2',
486 l => 'tile_path_4',
487 );
488
489 defined $dir_to_path{$dir}
490 or return;
491 my $map = $self->{map}{map}{info}{$dir_to_path{$dir}}
492 or return;
493
494 $map = map2abs ($map, $self);
495 $::MAINWIN->open_map_editor ($map);
496}
497
498sub open_map_prop {
499 my ($self) = @_;
500
501
502 my $w = Gtk2::Window->new ('toplevel');
503 $w->set_default_size (500, 500);
504 $w->add (my $sw = Gtk2::ScrolledWindow->new);
505 $sw->add_with_viewport (my $v = Gtk2::VBox->new);
506 $sw->set_policy ('automatic', 'automatic');
507 $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
508
509 my $i = 0;
510 for (
511 [qw/name Name string/],
512 [qw/region Region string/],
513 [qw/enter_x Enter-x string/],
514 [qw/enter_y Enter-y string/],
515 [qw/reset_timeout Reset-timeout string/],
516 [qw/swap_time Swap-timeout string/],
517 [qw/x x sep/],
518 [qw/difficulty Difficulty string/],
519 [qw/windspeed Windspeed string/],
520 [qw/pressure Pressure string/],
521 [qw/humid Humid string/],
522 [qw/temp Temp string/],
523 [qw/darkness Darkness string/],
524 [qw/sky Sky string/],
525 [qw/winddir Winddir string/],
526 [qw/x x sep/],
527 [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
528 [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
529 [qw/x x sep/],
530 [qw/msg Text text/],
531 [qw/maplore Maplore text/],
532 [qw/outdoor Outdoor check/],
533 [qw/unique Unique check/],
534 [qw/fixed_resettime Fixed-resettime check/],
535 [qw/x x sep/],
536 [qw/tile_path_1 Northpath string/],
537 [qw/tile_path_2 Eastpath string/],
538 [qw/tile_path_3 Southpath string/],
539 [qw/tile_path_4 Westpath string/],
540 [qw/tile_path_5 Toppath string/],
541 [qw/tile_path_6 Bottompath string/],
542 )
543 {
544 $self->_add_prop_entry ($t, $i++, @$_);
545 }
546
547 $w->show_all;
185} 548}
186 549
187=head1 AUTHOR 550=head1 AUTHOR
188 551
189 Marc Lehmann <schmorp@schmorp.de> 552 Marc Lehmann <schmorp@schmorp.de>

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines