ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.34
Committed: Tue Apr 4 10:27:22 2006 UTC (18 years, 2 months ago) by elmex
Branch: MAIN
Changes since 1.33: +57 -12 lines
Log Message:
Hopefully fixed this evil undo-bug. Where undo stopped working after hitting ctrl.

File Contents

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