ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.36
Committed: Thu Jun 1 14:00:04 2006 UTC (18 years ago) by elmex
Branch: MAIN
Changes since 1.35: +4 -2 lines
Log Message:
fixed a (design) bug with the editactions which caused
a messup with undo/change events. as the code became cleaner
and clearer i suspect it works now as expected :-)

File Contents

# User Rev Content
1 elmex 1.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 root 1.3 use Crossfire::Map;
15 elmex 1.1 use Crossfire::MapWidget;
16    
17     use GCE::AttrEdit;
18 elmex 1.22 use GCE::Util;
19 elmex 1.1
20 elmex 1.4 use Glib::Object::Subclass
21 elmex 1.6 Gtk2::Window;
22 elmex 1.1
23     use strict;
24    
25 elmex 1.35 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 elmex 1.36 $::MAINWIN->{edit_collection}{followexit}->edit ($map, $x, $y, $self)
35 elmex 1.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 root 1.9 sub build_menu {
47     my ($self) = @_;
48    
49     my $menu_tree = [
50     _File => {
51     item_type => '<Branch>',
52     children => [
53     "_Save" => {
54 root 1.10 callback => sub { $self->save_map },
55 root 1.9 accelerator => '<ctrl>S'
56     },
57 root 1.10 "Save As" => {
58     callback => sub { $self->save_map_as },
59     },
60 elmex 1.32 "_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 root 1.13 "Close" => {
69 elmex 1.31 callback => sub { $self->destroy },
70 root 1.13 },
71 root 1.10 ]
72 root 1.9 },
73     _Edit => {
74     item_type => '<Branch>',
75     children => [
76     "_Undo" => {
77     callback => sub { $self->undo },
78 elmex 1.17 accelerator => "<ctrl>Z"
79 root 1.9 },
80     "_Redo" => {
81     callback => sub { $self->redo },
82 elmex 1.17 accelerator => "<ctrl>Y"
83     },
84 root 1.9 ]
85     },
86 elmex 1.27 _Go => {
87     item_type => '<Branch>',
88     children => [
89     "_Up" => {
90     callback => sub { $self->follow ('u') },
91 elmex 1.33 accelerator => "<ctrl>Up"
92 elmex 1.27 },
93     "_Down" => {
94     callback => sub { $self->follow ('d') },
95 elmex 1.33 accelerator => "<ctrl>Down"
96 elmex 1.27 },
97     "_Right" => {
98     callback => sub { $self->follow ('r') },
99 elmex 1.33 accelerator => "<ctrl>Right"
100 elmex 1.27 },
101     "_Left" => {
102     callback => sub { $self->follow ('l') },
103 elmex 1.33 accelerator => "<ctrl>Left"
104 elmex 1.27 },
105     ]
106     },
107 elmex 1.29 _Help => {
108     item_type => '<Branch>',
109     children => [
110 elmex 1.30 _Manual => {
111 elmex 1.29 callback => sub { $::MAINWIN->show_help_window },
112     accelerator => "<ctrl>H"
113     },
114     ]
115     },
116 root 1.9 ];
117    
118     my $men =
119     Gtk2::SimpleMenu->new (
120     menu_tree => $menu_tree,
121     default_callback => \&default_cb,
122     );
123    
124 elmex 1.23 for (
125     [i => 'pick'],
126     [p => 'place'],
127 elmex 1.24 [e => 'erase'],
128     [s => 'select'],
129     [l => 'eval'],
130     [x => 'connectexit'],
131     [f => 'followexit']
132 elmex 1.23 )
133     {
134 elmex 1.24 my $tool = $_->[1];
135 elmex 1.23 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
136 elmex 1.24 sub { $::MAINWIN->set_edit_tool ($tool) });
137 elmex 1.23 }
138    
139 root 1.9 $self->add_accel_group ($men->{accel_group});
140    
141     return $men->{widget};
142     }
143    
144 elmex 1.31 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 elmex 1.34 } 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 elmex 1.31 }
155     }
156    
157 elmex 1.19 sub ea {
158     my ($self) = @_;
159 elmex 1.31 $self->{ea_alt} || $self->{etool};
160 elmex 1.19 }
161    
162 elmex 1.1 sub INIT_INSTANCE {
163     my ($self) = @_;
164    
165 elmex 1.6 $self->set_title ('gce - map editor');
166     $self->add (my $vb = Gtk2::VBox->new);
167 elmex 1.1
168 root 1.9 $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 elmex 1.31 $map->signal_connect_after (key_press_event => sub {
173 elmex 1.19 my ($map, $event) = @_;
174 root 1.9
175 elmex 1.19 my $kv = $event->keyval;
176 root 1.9
177 elmex 1.31 my $ret = 0;
178    
179 elmex 1.23 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 elmex 1.28 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
184 elmex 1.24 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
185 elmex 1.23 )
186     {
187 elmex 1.34 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 elmex 1.31 $ret = 1;
197 elmex 1.34
198     $self->start_drawmode ($map)
199     if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
200 elmex 1.23 }
201 elmex 1.19 }
202 root 1.9
203 elmex 1.19 if ($self->ea->special_arrow) {
204     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
205 elmex 1.34 } else {
206     # FIXME: Get the original cursor and insert it here
207     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
208 elmex 1.19 }
209 elmex 1.31
210     $ret
211 elmex 1.19 });
212 elmex 1.31
213     $map->signal_connect_after (key_release_event => sub {
214 elmex 1.19 my ($map, $event) = @_;
215    
216 elmex 1.31 my $ret = 0;
217    
218 elmex 1.19 if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L}
219     or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L})
220     {
221 elmex 1.34 my $was_in_draw = defined $self->{draw_mode};
222    
223     $self->stop_drawmode ($map)
224     if $was_in_draw;
225    
226 elmex 1.19 delete $self->{ea_alt};
227 elmex 1.31 $ret = 1;
228 elmex 1.34
229     $self->start_drawmode ($map)
230     if $was_in_draw;
231 elmex 1.19 }
232    
233     if ($self->ea->special_arrow) {
234     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
235     } else {
236 elmex 1.31 # FIXME: Get the original cursor and insert it here
237 elmex 1.19 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
238     }
239 elmex 1.31
240     $ret
241 elmex 1.19 });
242 elmex 1.31 $map->signal_connect_after (button_press_event => sub {
243 elmex 1.19 my ($map, $event) = @_;
244 elmex 1.1
245 elmex 1.31 if ((not $self->{draw_mode}) and $event->button == 1) {
246 elmex 1.19 my $ea = $self->ea;
247 elmex 1.4
248 elmex 1.34 $self->start_drawmode ($map);
249 root 1.9
250 elmex 1.19 $ea->want_cursor
251     or $map->disable_tooltip;
252 root 1.9
253 elmex 1.19 return 1;
254 elmex 1.35 } elsif ($event->button == 3) {
255     $self->do_context_menu ($map, $event);
256     return 1;
257 elmex 1.19 }
258 elmex 1.31
259 elmex 1.19 0
260 root 1.9 });
261    
262     $map->signal_connect_after (motion_notify_event => sub {
263     my ($map, $event) = @_;
264    
265     $self->{draw_mode}
266     or return;
267    
268 elmex 1.19 my $ea = $self->ea;
269    
270     my ($X, $Y) = @{$self->{draw_mode}}[0,1];
271 root 1.9 my ($x, $y) = $map->coord ($map->get_pointer);
272 elmex 1.4
273 root 1.9 while ($x != $X || $y != $Y) {
274 elmex 1.4
275 root 1.9 $X++ if $X < $x;
276     $X-- if $X > $x;
277     $Y++ if $Y < $y;
278     $Y-- if $Y > $y;
279 elmex 1.4
280 elmex 1.22 $ea->edit ($map, $X, $Y, $self)
281 elmex 1.15 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
282 root 1.9 }
283 elmex 1.4
284 elmex 1.19 @{$self->{draw_mode}}[0,1] = ($X, $Y);
285 elmex 1.1
286 root 1.9 1
287     });
288 elmex 1.1
289 elmex 1.31 $map->signal_connect_after (button_release_event => sub {
290 root 1.9 my ($map, $event) = @_;
291 elmex 1.1
292 elmex 1.31 if ($self->{draw_mode} and $event->button == 1) {
293 elmex 1.19 my $ea = $self->ea;
294 elmex 1.1
295 elmex 1.34 $self->stop_drawmode ($map);
296 elmex 1.4
297 root 1.9 $ea->want_cursor
298     or $map->enable_tooltip;
299 elmex 1.1
300 root 1.9 return 1;
301     }
302 elmex 1.1
303 root 1.9 0
304     });
305     }
306 elmex 1.8
307 elmex 1.34 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 elmex 1.36 $ea->begin ($map, $x, $y, $self);
318    
319     $ea->edit ($map, $x, $y, $self)
320 elmex 1.34 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    
325     sub stop_drawmode {
326     my ($self, $map) = @_;
327    
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 elmex 1.16 # FIXME: Fix the automatic update of the attribute editor! and also the stack view!
339 root 1.9 sub undo {
340     my ($self) = @_;
341 elmex 1.8
342 root 1.9 my $map = $self->{map}; # the Crossfire::MapWidget
343 elmex 1.1
344 root 1.9 $map->{undo_stack_pos}
345     or return;
346 elmex 1.1
347 root 1.9 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
348     }
349 elmex 1.1
350 root 1.9 sub redo {
351     my ($self) = @_;
352 elmex 1.7
353 root 1.9 my $map = $self->{map}; # the Crossfire::MapWidget
354 elmex 1.7
355 elmex 1.14 $map->{undo_stack}
356     and $map->{undo_stack_pos} < @{$map->{undo_stack}}
357     or return;
358 elmex 1.1
359 root 1.9 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
360 root 1.3 }
361    
362     sub open_map {
363 elmex 1.26 my ($self, $path, $key) = @_;
364    
365     $self->{mapkey} = $key;
366 root 1.3
367 elmex 1.18 if (ref $path) {
368     $self->{map}->set_map ($path);
369    
370     } else {
371     $self->{path} = $path;
372 elmex 1.17 # print "OPENMAP $path\n";
373 elmex 1.18 $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     }
377 root 1.3 }
378    
379 root 1.13 sub save_map {
380     my ($self) = @_;
381    
382 elmex 1.19 if ($self->{path}) {
383     $self->{map}{map}->write_file ($self->{path});
384 elmex 1.22 quick_msg ($self, "saved to $self->{path}");
385     } else {
386     $self->save_map_as;
387 elmex 1.19 }
388 root 1.13 }
389    
390     sub save_map_as {
391     my ($self) = @_;
392 elmex 1.19
393 elmex 1.22 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
394 elmex 1.19
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 elmex 1.22 quick_msg ($self, "saved to $self->{path}");
402 elmex 1.19 }
403    
404     $fc->destroy;
405 root 1.13 }
406    
407 elmex 1.17 sub _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 elmex 1.27 } 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 elmex 1.17 } else {
446     $edwid = Gtk2::Label->new ("FOO");
447     }
448     }
449    
450     sub 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 elmex 1.19 $self->{map}->invalidate_all;
468     $w->destroy;
469 elmex 1.17 }
470     ],
471     )
472     {
473     $self->_add_prop_entry ($t, $i++, @$_);
474     }
475    
476     $w->show_all;
477     }
478    
479 elmex 1.27 sub follow {
480     my ($self, $dir) = @_;
481    
482     my %dir_to_path = (
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    
498 elmex 1.17 sub 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 elmex 1.27 [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 elmex 1.17 )
543     {
544     $self->_add_prop_entry ($t, $i++, @$_);
545     }
546    
547     $w->show_all;
548     }
549    
550 elmex 1.1 =head1 AUTHOR
551    
552     Marc Lehmann <schmorp@schmorp.de>
553     http://home.schmorp.de/
554    
555     Robin Redeker <elmex@ta-sa.org>
556     http://www.ta-sa.org/
557    
558     =cut
559     1;
560