ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/cf.schmorp.de/gde/GCE/MapEditor.pm
Revision: 1.34
Committed: Tue Apr 4 10:27:22 2006 UTC (20 years, 6 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

# 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 root 1.9 sub build_menu {
26     my ($self) = @_;
27    
28     my $menu_tree = [
29     _File => {
30     item_type => '<Branch>',
31     children => [
32     "_Save" => {
33 root 1.10 callback => sub { $self->save_map },
34 root 1.9 accelerator => '<ctrl>S'
35     },
36 root 1.10 "Save As" => {
37     callback => sub { $self->save_map_as },
38     },
39 elmex 1.32 "_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 root 1.13 "Close" => {
48 elmex 1.31 callback => sub { $self->destroy },
49 root 1.13 },
50 root 1.10 ]
51 root 1.9 },
52     _Edit => {
53     item_type => '<Branch>',
54     children => [
55     "_Undo" => {
56     callback => sub { $self->undo },
57 elmex 1.17 accelerator => "<ctrl>Z"
58 root 1.9 },
59     "_Redo" => {
60     callback => sub { $self->redo },
61 elmex 1.17 accelerator => "<ctrl>Y"
62     },
63 root 1.9 ]
64     },
65 elmex 1.27 _Go => {
66     item_type => '<Branch>',
67     children => [
68     "_Up" => {
69     callback => sub { $self->follow ('u') },
70 elmex 1.33 accelerator => "<ctrl>Up"
71 elmex 1.27 },
72     "_Down" => {
73     callback => sub { $self->follow ('d') },
74 elmex 1.33 accelerator => "<ctrl>Down"
75 elmex 1.27 },
76     "_Right" => {
77     callback => sub { $self->follow ('r') },
78 elmex 1.33 accelerator => "<ctrl>Right"
79 elmex 1.27 },
80     "_Left" => {
81     callback => sub { $self->follow ('l') },
82 elmex 1.33 accelerator => "<ctrl>Left"
83 elmex 1.27 },
84     ]
85     },
86 elmex 1.29 _Help => {
87     item_type => '<Branch>',
88     children => [
89 elmex 1.30 _Manual => {
90 elmex 1.29 callback => sub { $::MAINWIN->show_help_window },
91     accelerator => "<ctrl>H"
92     },
93     ]
94     },
95 root 1.9 ];
96    
97     my $men =
98     Gtk2::SimpleMenu->new (
99     menu_tree => $menu_tree,
100     default_callback => \&default_cb,
101     );
102    
103 elmex 1.23 for (
104     [i => 'pick'],
105     [p => 'place'],
106 elmex 1.24 [e => 'erase'],
107     [s => 'select'],
108     [l => 'eval'],
109     [x => 'connectexit'],
110     [f => 'followexit']
111 elmex 1.23 )
112     {
113 elmex 1.24 my $tool = $_->[1];
114 elmex 1.23 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
115 elmex 1.24 sub { $::MAINWIN->set_edit_tool ($tool) });
116 elmex 1.23 }
117    
118 root 1.9 $self->add_accel_group ($men->{accel_group});
119    
120     return $men->{widget};
121     }
122    
123 elmex 1.31 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 elmex 1.34 } 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 elmex 1.31 }
134     }
135    
136 elmex 1.19 sub ea {
137     my ($self) = @_;
138 elmex 1.31 $self->{ea_alt} || $self->{etool};
139 elmex 1.19 }
140    
141 elmex 1.1 sub INIT_INSTANCE {
142     my ($self) = @_;
143    
144 elmex 1.6 $self->set_title ('gce - map editor');
145     $self->add (my $vb = Gtk2::VBox->new);
146 elmex 1.1
147 root 1.9 $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 elmex 1.31 $map->signal_connect_after (key_press_event => sub {
152 elmex 1.19 my ($map, $event) = @_;
153 root 1.9
154 elmex 1.19 my $kv = $event->keyval;
155 root 1.9
156 elmex 1.31 my $ret = 0;
157    
158 elmex 1.23 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 elmex 1.28 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
163 elmex 1.24 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
164 elmex 1.23 )
165     {
166 elmex 1.34 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 elmex 1.31 $ret = 1;
176 elmex 1.34
177     $self->start_drawmode ($map)
178     if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
179 elmex 1.23 }
180 elmex 1.19 }
181 root 1.9
182 elmex 1.19 if ($self->ea->special_arrow) {
183     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
184 elmex 1.34 } else {
185     # FIXME: Get the original cursor and insert it here
186     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
187 elmex 1.19 }
188 elmex 1.31
189     $ret
190 elmex 1.19 });
191 elmex 1.31
192     $map->signal_connect_after (key_release_event => sub {
193 elmex 1.19 my ($map, $event) = @_;
194    
195 elmex 1.31 my $ret = 0;
196    
197 elmex 1.19 if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L}
198     or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L})
199     {
200 elmex 1.34 my $was_in_draw = defined $self->{draw_mode};
201    
202     $self->stop_drawmode ($map)
203     if $was_in_draw;
204    
205 elmex 1.19 delete $self->{ea_alt};
206 elmex 1.31 $ret = 1;
207 elmex 1.34
208     $self->start_drawmode ($map)
209     if $was_in_draw;
210 elmex 1.19 }
211    
212     if ($self->ea->special_arrow) {
213     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
214     } else {
215 elmex 1.31 # FIXME: Get the original cursor and insert it here
216 elmex 1.19 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
217     }
218 elmex 1.31
219     $ret
220 elmex 1.19 });
221 elmex 1.31 $map->signal_connect_after (button_press_event => sub {
222 elmex 1.19 my ($map, $event) = @_;
223 elmex 1.1
224 elmex 1.31 if ((not $self->{draw_mode}) and $event->button == 1) {
225 elmex 1.19 my $ea = $self->ea;
226 elmex 1.4
227 elmex 1.34 $self->start_drawmode ($map);
228 root 1.9
229 elmex 1.19 $ea->want_cursor
230     or $map->disable_tooltip;
231 root 1.9
232 elmex 1.19 return 1;
233     }
234 elmex 1.31
235 elmex 1.19 0
236 root 1.9 });
237    
238     $map->signal_connect_after (motion_notify_event => sub {
239     my ($map, $event) = @_;
240    
241     $self->{draw_mode}
242     or return;
243    
244 elmex 1.19 my $ea = $self->ea;
245    
246     my ($X, $Y) = @{$self->{draw_mode}}[0,1];
247 root 1.9 my ($x, $y) = $map->coord ($map->get_pointer);
248 elmex 1.4
249 root 1.9 while ($x != $X || $y != $Y) {
250 elmex 1.4
251 root 1.9 $X++ if $X < $x;
252     $X-- if $X > $x;
253     $Y++ if $Y < $y;
254     $Y-- if $Y > $y;
255 elmex 1.4
256 elmex 1.22 $ea->edit ($map, $X, $Y, $self)
257 elmex 1.15 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
258 root 1.9 }
259 elmex 1.4
260 elmex 1.19 @{$self->{draw_mode}}[0,1] = ($X, $Y);
261 elmex 1.1
262 root 1.9 1
263     });
264 elmex 1.1
265 elmex 1.31 $map->signal_connect_after (button_release_event => sub {
266 root 1.9 my ($map, $event) = @_;
267 elmex 1.1
268 elmex 1.31 if ($self->{draw_mode} and $event->button == 1) {
269 elmex 1.19 my $ea = $self->ea;
270 elmex 1.1
271 elmex 1.34 $self->stop_drawmode ($map);
272 elmex 1.4
273 root 1.9 $ea->want_cursor
274     or $map->enable_tooltip;
275 elmex 1.1
276 root 1.9 return 1;
277     }
278 elmex 1.1
279 root 1.9 0
280     });
281     }
282 elmex 1.8
283 elmex 1.34 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 elmex 1.16 # FIXME: Fix the automatic update of the attribute editor! and also the stack view!
313 root 1.9 sub undo {
314     my ($self) = @_;
315 elmex 1.8
316 root 1.9 my $map = $self->{map}; # the Crossfire::MapWidget
317 elmex 1.1
318 root 1.9 $map->{undo_stack_pos}
319     or return;
320 elmex 1.1
321 root 1.9 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
322     }
323 elmex 1.1
324 root 1.9 sub redo {
325     my ($self) = @_;
326 elmex 1.7
327 root 1.9 my $map = $self->{map}; # the Crossfire::MapWidget
328 elmex 1.7
329 elmex 1.14 $map->{undo_stack}
330     and $map->{undo_stack_pos} < @{$map->{undo_stack}}
331     or return;
332 elmex 1.1
333 root 1.9 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
334 root 1.3 }
335    
336     sub open_map {
337 elmex 1.26 my ($self, $path, $key) = @_;
338    
339     $self->{mapkey} = $key;
340 root 1.3
341 elmex 1.18 if (ref $path) {
342     $self->{map}->set_map ($path);
343    
344     } else {
345     $self->{path} = $path;
346 elmex 1.17 # print "OPENMAP $path\n";
347 elmex 1.18 $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 root 1.3 }
352    
353 root 1.13 sub save_map {
354     my ($self) = @_;
355    
356 elmex 1.19 if ($self->{path}) {
357     $self->{map}{map}->write_file ($self->{path});
358 elmex 1.22 quick_msg ($self, "saved to $self->{path}");
359     } else {
360     $self->save_map_as;
361 elmex 1.19 }
362 root 1.13 }
363    
364     sub save_map_as {
365     my ($self) = @_;
366 elmex 1.19
367 elmex 1.22 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
368 elmex 1.19
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 elmex 1.22 quick_msg ($self, "saved to $self->{path}");
376 elmex 1.19 }
377    
378     $fc->destroy;
379 root 1.13 }
380    
381 elmex 1.17 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 elmex 1.27 } 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 elmex 1.17 } 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 elmex 1.19 $self->{map}->invalidate_all;
442     $w->destroy;
443 elmex 1.17 }
444     ],
445     )
446     {
447     $self->_add_prop_entry ($t, $i++, @$_);
448     }
449    
450     $w->show_all;
451     }
452    
453 elmex 1.27 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 elmex 1.17 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 elmex 1.27 [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 elmex 1.17 )
517     {
518     $self->_add_prop_entry ($t, $i++, @$_);
519     }
520    
521     $w->show_all;
522     }
523    
524 elmex 1.1 =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