ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.35
Committed: Tue Apr 4 11:29:54 2006 UTC (18 years, 2 months ago) by elmex
Branch: MAIN
Changes since 1.34: +24 -0 lines
Log Message:
Removed pick and made the attribut editor 'the current pick'.
Added context menu to map view with an follow item in it.

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 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}->begin ($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
46 sub 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 [x => 'connectexit'],
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
144 sub 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
157 sub ea {
158 my ($self) = @_;
159 $self->{ea_alt} || $self->{etool};
160 }
161
162 sub INIT_INSTANCE {
163 my ($self) = @_;
164
165 $self->set_title ('gce - map editor');
166 $self->add (my $vb = Gtk2::VBox->new);
167
168 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
169
170 $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0);
171
172 $map->signal_connect_after (key_press_event => sub {
173 my ($map, $event) = @_;
174
175 my $kv = $event->keyval;
176
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
207 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
208 }
209
210 $ret
211 });
212
213 $map->signal_connect_after (key_release_event => sub {
214 my ($map, $event) = @_;
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
237 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
238 }
239
240 $ret
241 });
242 $map->signal_connect_after (button_press_event => sub {
243 my ($map, $event) = @_;
244
245 if ((not $self->{draw_mode}) and $event->button == 1) {
246 my $ea = $self->ea;
247
248 $self->start_drawmode ($map);
249
250 $ea->want_cursor
251 or $map->disable_tooltip;
252
253 return 1;
254 } elsif ($event->button == 3) {
255 $self->do_context_menu ($map, $event);
256 return 1;
257 }
258
259 0
260 });
261
262 $map->signal_connect_after (motion_notify_event => sub {
263 my ($map, $event) = @_;
264
265 $self->{draw_mode}
266 or return;
267
268 my $ea = $self->ea;
269
270 my ($X, $Y) = @{$self->{draw_mode}}[0,1];
271 my ($x, $y) = $map->coord ($map->get_pointer);
272
273 while ($x != $X || $y != $Y) {
274
275 $X++ if $X < $x;
276 $X-- if $X > $x;
277 $Y++ if $Y < $y;
278 $Y-- if $Y > $y;
279
280 $ea->edit ($map, $X, $Y, $self)
281 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
282 }
283
284 @{$self->{draw_mode}}[0,1] = ($X, $Y);
285
286 1
287 });
288
289 $map->signal_connect_after (button_release_event => sub {
290 my ($map, $event) = @_;
291
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 }
302
303 0
304 });
305 }
306
307 sub start_drawmode {
308 my ($self, $map) = @_;
309
310 $self->{draw_mode} and return;
311
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 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
319
320 $self->{draw_mode} = [$x, $y];
321 }
322
323 sub stop_drawmode {
324 my ($self, $map) = @_;
325
326 $self->{draw_mode} or return;
327
328 my ($x, $y) = $map->coord ($map->get_pointer);
329
330 my $ea = $self->ea;
331 $ea->end ($map, $x, $y, $self);
332
333 delete $self->{draw_mode};
334 }
335
336 # FIXME: Fix the automatic update of the attribute editor! and also the stack view!
337 sub undo {
338 my ($self) = @_;
339
340 my $map = $self->{map}; # the Crossfire::MapWidget
341
342 $map->{undo_stack_pos}
343 or return;
344
345 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
346 }
347
348 sub redo {
349 my ($self) = @_;
350
351 my $map = $self->{map}; # the Crossfire::MapWidget
352
353 $map->{undo_stack}
354 and $map->{undo_stack_pos} < @{$map->{undo_stack}}
355 or return;
356
357 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
358 }
359
360 sub open_map {
361 my ($self, $path, $key) = @_;
362
363 $self->{mapkey} = $key;
364
365 if (ref $path) {
366 $self->{map}->set_map ($path);
367
368 } else {
369 $self->{path} = $path;
370 # print "OPENMAP $path\n";
371 $self->{map}->set_map (my $m = new_from_file Crossfire::Map $path);
372 require Data::Dumper;
373 # print "FOO:" .Data::Dumper::Dumper ($m) . "\n";
374 }
375 }
376
377 sub save_map {
378 my ($self) = @_;
379
380 if ($self->{path}) {
381 $self->{map}{map}->write_file ($self->{path});
382 quick_msg ($self, "saved to $self->{path}");
383 } else {
384 $self->save_map_as;
385 }
386 }
387
388 sub save_map_as {
389 my ($self) = @_;
390
391 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
392
393 if ('ok' eq $fc->run) {
394
395 $::MAINWIN->{fc_last_folder} = $fc->get_current_folder;
396 $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++;
397
398 $self->{map}{map}->write_file ($self->{path} = $fc->get_filename);
399 quick_msg ($self, "saved to $self->{path}");
400 }
401
402 $fc->destroy;
403 }
404
405 sub _add_prop_entry {
406 my ($self, $table, $idx, $key, $desc, $type, $changecb) = @_;
407
408 my $edwid;
409
410 if ($type eq 'string') {
411 $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
412 $edwid = Gtk2::Entry->new;
413 $edwid->set_text ($self->{map}{map}{info}{$key});
414 $edwid->signal_connect (changed => sub {
415 $self->{map}{map}{info}{$key} = $_[0]->get_text;
416 if ($changecb) {
417 $changecb->($_[0]->get_text);
418 }
419 });
420 $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
421
422 } elsif ($type eq 'button') {
423 $table->attach_defaults (my $b = Gtk2::Button->new_with_label ($desc), 0, 2, $idx, $idx + 1);
424 $b->signal_connect (clicked => ($changecb || sub {}));
425
426 } elsif ($type eq 'label') {
427 $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
428 $edwid = Gtk2::Label->new ($self->{map}{map}{info}{$key});
429 $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
430
431 } elsif ($type eq 'check') {
432 $table->attach_defaults (my $lbl1 = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
433 $table->attach_defaults (my $lbl = Gtk2::CheckButton->new, 1, 2, $idx, $idx + 1);
434 $lbl->set_active ($self->{map}{map}{info}{$key});
435 $lbl->signal_connect (toggled => sub {
436 my ($lbl) = @_;
437 $self->{map}{map}{info}{$key} = $lbl->get_active * 1;
438 ($changecb || sub {})->($lbl->get_active);
439 });
440
441 } elsif ($type eq 'sep') {
442 $table->attach_defaults (my $lbl1 = Gtk2::HSeparator->new, 0, 2, $idx, $idx + 1);
443 } else {
444 $edwid = Gtk2::Label->new ("FOO");
445 }
446 }
447
448 sub open_resize_map {
449 my ($self) = @_;
450
451 my $w = Gtk2::Window->new ('toplevel');
452 $w->set_default_size (250, 150);
453 $w->add (my $sw = Gtk2::ScrolledWindow->new);
454 $sw->add_with_viewport (my $v = Gtk2::VBox->new);
455 $sw->set_policy ('automatic', 'automatic');
456 $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
457
458 my $i = 0;
459 for (
460 [qw/width Width string/],
461 [qw/height Height string/],
462 [qw/save Save button/,
463 sub {
464 $self->{map}{map}->resize ($self->{map}{map}{info}{width}, $self->{map}{map}{info}{height});
465 $self->{map}->invalidate_all;
466 $w->destroy;
467 }
468 ],
469 )
470 {
471 $self->_add_prop_entry ($t, $i++, @$_);
472 }
473
474 $w->show_all;
475 }
476
477 sub follow {
478 my ($self, $dir) = @_;
479
480 my %dir_to_path = (
481 u => 'tile_path_1',
482 d => 'tile_path_3',
483 r => 'tile_path_2',
484 l => 'tile_path_4',
485 );
486
487 defined $dir_to_path{$dir}
488 or return;
489 my $map = $self->{map}{map}{info}{$dir_to_path{$dir}}
490 or return;
491
492 $map = map2abs ($map, $self);
493 $::MAINWIN->open_map_editor ($map);
494 }
495
496 sub open_map_prop {
497 my ($self) = @_;
498
499
500 my $w = Gtk2::Window->new ('toplevel');
501 $w->set_default_size (500, 500);
502 $w->add (my $sw = Gtk2::ScrolledWindow->new);
503 $sw->add_with_viewport (my $v = Gtk2::VBox->new);
504 $sw->set_policy ('automatic', 'automatic');
505 $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
506
507 my $i = 0;
508 for (
509 [qw/name Name string/],
510 [qw/region Region string/],
511 [qw/enter_x Enter-x string/],
512 [qw/enter_y Enter-y string/],
513 [qw/reset_timeout Reset-timeout string/],
514 [qw/swap_time Swap-timeout string/],
515 [qw/x x sep/],
516 [qw/difficulty Difficulty string/],
517 [qw/windspeed Windspeed string/],
518 [qw/pressure Pressure string/],
519 [qw/humid Humid string/],
520 [qw/temp Temp string/],
521 [qw/darkness Darkness string/],
522 [qw/sky Sky string/],
523 [qw/winddir Winddir string/],
524 [qw/x x sep/],
525 [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
526 [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
527 [qw/x x sep/],
528 [qw/msg Text text/],
529 [qw/maplore Maplore text/],
530 [qw/outdoor Outdoor check/],
531 [qw/unique Unique check/],
532 [qw/fixed_resettime Fixed-resettime check/],
533 [qw/x x sep/],
534 [qw/tile_path_1 Northpath string/],
535 [qw/tile_path_2 Eastpath string/],
536 [qw/tile_path_3 Southpath string/],
537 [qw/tile_path_4 Westpath string/],
538 [qw/tile_path_5 Toppath string/],
539 [qw/tile_path_6 Bottompath string/],
540 )
541 {
542 $self->_add_prop_entry ($t, $i++, @$_);
543 }
544
545 $w->show_all;
546 }
547
548 =head1 AUTHOR
549
550 Marc Lehmann <schmorp@schmorp.de>
551 http://home.schmorp.de/
552
553 Robin Redeker <elmex@ta-sa.org>
554 http://www.ta-sa.org/
555
556 =cut
557 1;
558