ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/cf.schmorp.de/gde/GCE/MapEditor.pm
Revision: 1.31
Committed: Sun Mar 26 15:41:59 2006 UTC (20 years, 7 months ago) by elmex
Branch: MAIN
Changes since 1.30: +33 -39 lines
Log Message:
fixed hopefully some modifier bugs and made code easier

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 root 1.13 "Close" => {
40 elmex 1.31 callback => sub { $self->destroy },
41 root 1.13 },
42 root 1.10 ]
43 root 1.9 },
44     _Edit => {
45     item_type => '<Branch>',
46     children => [
47     "_Undo" => {
48     callback => sub { $self->undo },
49 elmex 1.17 accelerator => "<ctrl>Z"
50 root 1.9 },
51     "_Redo" => {
52     callback => sub { $self->redo },
53 elmex 1.17 accelerator => "<ctrl>Y"
54     },
55     "_Map Properties" => {
56     callback => sub { $self->open_map_prop },
57     accelerator => "<ctrl>P"
58     },
59     "_Map Resize" => {
60     callback => sub { $self->open_resize_map },
61     accelerator => "<ctrl>R"
62 root 1.9 },
63     ]
64     },
65 elmex 1.27 _Go => {
66     item_type => '<Branch>',
67     children => [
68     "_Up" => {
69     callback => sub { $self->follow ('u') },
70     accelerator => "<ctrl>U"
71     },
72     "_Down" => {
73     callback => sub { $self->follow ('d') },
74     accelerator => "<ctrl>D"
75     },
76     "_Right" => {
77     callback => sub { $self->follow ('r') },
78     accelerator => "<ctrl>R"
79     },
80     "_Left" => {
81     callback => sub { $self->follow ('l') },
82     accelerator => "<ctrl>L"
83     },
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     }
131     }
132    
133 elmex 1.19 sub ea {
134     my ($self) = @_;
135 elmex 1.31 $self->{ea_alt} || $self->{etool};
136 elmex 1.19 }
137    
138 elmex 1.1 sub INIT_INSTANCE {
139     my ($self) = @_;
140    
141 elmex 1.6 $self->set_title ('gce - map editor');
142     $self->add (my $vb = Gtk2::VBox->new);
143 elmex 1.1
144 root 1.9 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
145    
146     $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0);
147    
148 elmex 1.31 $map->signal_connect_after (key_press_event => sub {
149 elmex 1.19 my ($map, $event) = @_;
150 root 1.9
151 elmex 1.19 my $kv = $event->keyval;
152 root 1.9
153 elmex 1.31 my $ret = 0;
154    
155 elmex 1.23 my ($x, $y) = $map->coord ($map->get_pointer);
156     for ([Control_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{erase} }],
157     [Alt_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{pick} }],
158     [c => sub { $::MAINWIN->{edit_collection}{select}->copy }],
159 elmex 1.28 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
160 elmex 1.24 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
161 elmex 1.23 )
162     {
163     if ($kv == $Gtk2::Gdk::Keysyms{$_->[0]}) {
164     $_->[1]->();
165 elmex 1.31 $ret = 1;
166 elmex 1.23 }
167 elmex 1.19 }
168 root 1.9
169 elmex 1.19 if ($self->ea->special_arrow) {
170     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
171     }
172 elmex 1.31
173     $ret
174 elmex 1.19 });
175 elmex 1.31
176     $map->signal_connect_after (key_release_event => sub {
177 elmex 1.19 my ($map, $event) = @_;
178    
179 elmex 1.31 my $ret = 0;
180    
181 elmex 1.19 if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L}
182     or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L})
183     {
184     delete $self->{ea_alt};
185 elmex 1.31 $ret = 1;
186 elmex 1.19 }
187    
188     if ($self->ea->special_arrow) {
189     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
190     } else {
191 elmex 1.31 # FIXME: Get the original cursor and insert it here
192 elmex 1.19 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
193     }
194 elmex 1.31
195     $ret
196 elmex 1.19 });
197 elmex 1.31 $map->signal_connect_after (button_press_event => sub {
198 elmex 1.19 my ($map, $event) = @_;
199 elmex 1.1
200 elmex 1.31 if ((not $self->{draw_mode}) and $event->button == 1) {
201     my ($x, $y) = $map->coord ($event->x, $event->y);
202     my $as = $map->get ($x, $y);
203 elmex 1.4
204 elmex 1.19 my $ea = $self->ea;
205 elmex 1.4
206 elmex 1.22 $ea->begin ($map, $x, $y, $self)
207 elmex 1.19 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
208    
209     $self->{draw_mode} = [$x, $y];
210 root 1.9
211 elmex 1.19 $ea->want_cursor
212     or $map->disable_tooltip;
213 root 1.9
214 elmex 1.19 return 1;
215     }
216 elmex 1.31
217 elmex 1.19 0
218 root 1.9 });
219    
220     $map->signal_connect_after (motion_notify_event => sub {
221     my ($map, $event) = @_;
222    
223     $self->{draw_mode}
224     or return;
225    
226 elmex 1.19 my $ea = $self->ea;
227    
228     my ($X, $Y) = @{$self->{draw_mode}}[0,1];
229 root 1.9 my ($x, $y) = $map->coord ($map->get_pointer);
230 elmex 1.4
231 root 1.9 while ($x != $X || $y != $Y) {
232 elmex 1.4
233 root 1.9 $X++ if $X < $x;
234     $X-- if $X > $x;
235     $Y++ if $Y < $y;
236     $Y-- if $Y > $y;
237 elmex 1.4
238 elmex 1.22 $ea->edit ($map, $X, $Y, $self)
239 elmex 1.15 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
240 root 1.9 }
241 elmex 1.4
242 elmex 1.19 @{$self->{draw_mode}}[0,1] = ($X, $Y);
243 elmex 1.1
244 root 1.9 1
245     });
246 elmex 1.1
247 elmex 1.31 $map->signal_connect_after (button_release_event => sub {
248 root 1.9 my ($map, $event) = @_;
249 elmex 1.1
250 elmex 1.31 if ($self->{draw_mode} and $event->button == 1) {
251 root 1.9 my ($x, $y) = $map->coord ($map->get_pointer);
252 elmex 1.4
253 elmex 1.19 my $ea = $self->ea;
254 elmex 1.22 $ea->end ($map, $x, $y, $self);
255 elmex 1.1
256 root 1.9 delete $self->{draw_mode};
257 elmex 1.4
258 root 1.9 $ea->want_cursor
259     or $map->enable_tooltip;
260 elmex 1.1
261 root 1.9 return 1;
262     }
263 elmex 1.1
264 root 1.9 0
265     });
266     }
267 elmex 1.8
268 elmex 1.16 # FIXME: Fix the automatic update of the attribute editor! and also the stack view!
269 root 1.9 sub undo {
270     my ($self) = @_;
271 elmex 1.8
272 root 1.9 my $map = $self->{map}; # the Crossfire::MapWidget
273 elmex 1.1
274 root 1.9 $map->{undo_stack_pos}
275     or return;
276 elmex 1.1
277 root 1.9 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
278     }
279 elmex 1.1
280 root 1.9 sub redo {
281     my ($self) = @_;
282 elmex 1.7
283 root 1.9 my $map = $self->{map}; # the Crossfire::MapWidget
284 elmex 1.7
285 elmex 1.14 $map->{undo_stack}
286     and $map->{undo_stack_pos} < @{$map->{undo_stack}}
287     or return;
288 elmex 1.1
289 root 1.9 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
290 root 1.3 }
291    
292     sub open_map {
293 elmex 1.26 my ($self, $path, $key) = @_;
294    
295     $self->{mapkey} = $key;
296 root 1.3
297 elmex 1.18 if (ref $path) {
298     $self->{map}->set_map ($path);
299    
300     } else {
301     $self->{path} = $path;
302 elmex 1.17 # print "OPENMAP $path\n";
303 elmex 1.18 $self->{map}->set_map (my $m = new_from_file Crossfire::Map $path);
304     require Data::Dumper;
305     # print "FOO:" .Data::Dumper::Dumper ($m) . "\n";
306     }
307 root 1.3 }
308    
309 root 1.13 sub save_map {
310     my ($self) = @_;
311    
312 elmex 1.19 if ($self->{path}) {
313     $self->{map}{map}->write_file ($self->{path});
314 elmex 1.22 quick_msg ($self, "saved to $self->{path}");
315     } else {
316     $self->save_map_as;
317 elmex 1.19 }
318 root 1.13 }
319    
320     sub save_map_as {
321     my ($self) = @_;
322 elmex 1.19
323 elmex 1.22 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
324 elmex 1.19
325     if ('ok' eq $fc->run) {
326    
327     $::MAINWIN->{fc_last_folder} = $fc->get_current_folder;
328     $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++;
329    
330     $self->{map}{map}->write_file ($self->{path} = $fc->get_filename);
331 elmex 1.22 quick_msg ($self, "saved to $self->{path}");
332 elmex 1.19 }
333    
334     $fc->destroy;
335 root 1.13 }
336    
337 elmex 1.17 sub _add_prop_entry {
338     my ($self, $table, $idx, $key, $desc, $type, $changecb) = @_;
339    
340     my $edwid;
341    
342     if ($type eq 'string') {
343     $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
344     $edwid = Gtk2::Entry->new;
345     $edwid->set_text ($self->{map}{map}{info}{$key});
346     $edwid->signal_connect (changed => sub {
347     $self->{map}{map}{info}{$key} = $_[0]->get_text;
348     if ($changecb) {
349     $changecb->($_[0]->get_text);
350     }
351     });
352     $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
353    
354     } elsif ($type eq 'button') {
355     $table->attach_defaults (my $b = Gtk2::Button->new_with_label ($desc), 0, 2, $idx, $idx + 1);
356     $b->signal_connect (clicked => ($changecb || sub {}));
357    
358     } elsif ($type eq 'label') {
359     $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
360     $edwid = Gtk2::Label->new ($self->{map}{map}{info}{$key});
361     $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
362    
363 elmex 1.27 } elsif ($type eq 'check') {
364     $table->attach_defaults (my $lbl1 = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
365     $table->attach_defaults (my $lbl = Gtk2::CheckButton->new, 1, 2, $idx, $idx + 1);
366     $lbl->set_active ($self->{map}{map}{info}{$key});
367     $lbl->signal_connect (toggled => sub {
368     my ($lbl) = @_;
369     $self->{map}{map}{info}{$key} = $lbl->get_active * 1;
370     ($changecb || sub {})->($lbl->get_active);
371     });
372    
373     } elsif ($type eq 'sep') {
374     $table->attach_defaults (my $lbl1 = Gtk2::HSeparator->new, 0, 2, $idx, $idx + 1);
375 elmex 1.17 } else {
376     $edwid = Gtk2::Label->new ("FOO");
377     }
378     }
379    
380     sub open_resize_map {
381     my ($self) = @_;
382    
383     my $w = Gtk2::Window->new ('toplevel');
384     $w->set_default_size (250, 150);
385     $w->add (my $sw = Gtk2::ScrolledWindow->new);
386     $sw->add_with_viewport (my $v = Gtk2::VBox->new);
387     $sw->set_policy ('automatic', 'automatic');
388     $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
389    
390     my $i = 0;
391     for (
392     [qw/width Width string/],
393     [qw/height Height string/],
394     [qw/save Save button/,
395     sub {
396     $self->{map}{map}->resize ($self->{map}{map}{info}{width}, $self->{map}{map}{info}{height});
397 elmex 1.19 $self->{map}->invalidate_all;
398     $w->destroy;
399 elmex 1.17 }
400     ],
401     )
402     {
403     $self->_add_prop_entry ($t, $i++, @$_);
404     }
405    
406     $w->show_all;
407     }
408    
409 elmex 1.27 sub follow {
410     my ($self, $dir) = @_;
411    
412     my %dir_to_path = (
413     u => 'tile_path_1',
414     d => 'tile_path_3',
415     r => 'tile_path_2',
416     l => 'tile_path_4',
417     );
418    
419     defined $dir_to_path{$dir}
420     or return;
421     my $map = $self->{map}{map}{info}{$dir_to_path{$dir}}
422     or return;
423    
424     $map = map2abs ($map, $self);
425     $::MAINWIN->open_map_editor ($map);
426     }
427    
428 elmex 1.17 sub open_map_prop {
429     my ($self) = @_;
430    
431    
432     my $w = Gtk2::Window->new ('toplevel');
433     $w->set_default_size (500, 500);
434     $w->add (my $sw = Gtk2::ScrolledWindow->new);
435     $sw->add_with_viewport (my $v = Gtk2::VBox->new);
436     $sw->set_policy ('automatic', 'automatic');
437     $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
438    
439     my $i = 0;
440     for (
441 elmex 1.27 [qw/name Name string/],
442     [qw/region Region string/],
443     [qw/enter_x Enter-x string/],
444     [qw/enter_y Enter-y string/],
445     [qw/reset_timeout Reset-timeout string/],
446     [qw/swap_time Swap-timeout string/],
447     [qw/x x sep/],
448     [qw/difficulty Difficulty string/],
449     [qw/windspeed Windspeed string/],
450     [qw/pressure Pressure string/],
451     [qw/humid Humid string/],
452     [qw/temp Temp string/],
453     [qw/darkness Darkness string/],
454     [qw/sky Sky string/],
455     [qw/winddir Winddir string/],
456     [qw/x x sep/],
457     [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
458     [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
459     [qw/x x sep/],
460     [qw/msg Text text/],
461     [qw/maplore Maplore text/],
462     [qw/outdoor Outdoor check/],
463     [qw/unique Unique check/],
464     [qw/fixed_resettime Fixed-resettime check/],
465     [qw/x x sep/],
466     [qw/tile_path_1 Northpath string/],
467     [qw/tile_path_2 Eastpath string/],
468     [qw/tile_path_3 Southpath string/],
469     [qw/tile_path_4 Westpath string/],
470     [qw/tile_path_5 Toppath string/],
471     [qw/tile_path_6 Bottompath string/],
472 elmex 1.17 )
473     {
474     $self->_add_prop_entry ($t, $i++, @$_);
475     }
476    
477     $w->show_all;
478     }
479    
480 elmex 1.1 =head1 AUTHOR
481    
482     Marc Lehmann <schmorp@schmorp.de>
483     http://home.schmorp.de/
484    
485     Robin Redeker <elmex@ta-sa.org>
486     http://www.ta-sa.org/
487    
488     =cut
489     1;
490